
Klant: Useq
Project: Organ atlas
Dataset: Kidney data
date: 14 41 Thu 06 October, 2022

loading dependencies Please make sure the following
packages are installed and required libraries can be loaded: *
install.packages(“pkgbuild”) // pkgbuild::check_build_tools() *
install.packages(“devtools”) *
devtools::install_github(“Nanostring-Biostats/NanoStringNCTools”) *
devtools::install_github(“Nanostring-Biostats/GeomxTools”, ref = “dev”)
* devtools::install_github(“Nanostring-Biostats/GeoMxWorkflows”, ref =
“main”) * BiocManager::install(“GeoMxWorkflows”) *
devtools::install_github(“DavisLaboratory/standR”) *
BiocManager::install(“SpatialDecon”) * BiocManager::install(“GSVA”) *
install.packages(“plotly”) * install.packages(“DT”) *
install.packages(“msigdbr”) #install.packages(“digest”)
#install.packages(“rmarkdown”) #install.packages(“kable”)
#load libraries
library(NanoStringNCTools)
library(GeomxTools)
library(GeoMxWorkflows)
library(SpatialDecon)
library(GSVA) #for pathway analyses
library(msigdbr) #for molecular signatures in pathway analyses
library(knitr)
library(dplyr)
library(ggforce)
library(ggplot2)
library(scales) # for percent
library(reshape2) # for melt
library(cowplot) # for plot_grid
library(umap)
library(Rtsne)
library(pheatmap) # for pheatmap
library(ggrepel)
library(scales) #for ggplot peaudolog to prevent errors on log(0)
library(DT)
library(plotly)
library(gridExtra)
library(RColorBrewer)
library(limma)
library(clusterProfiler) #pathway analysis
library(gage)
1 loading base files
# Reference the main folder 'file.path' containing the sub-folders with each data file type:
datadir<-file.path("C:/Users/pkloosterman/Documents/general_workflow/Kidney_Dataset")
#datadir<-file.path("C:/Users/pimkl/OneDrive/Documenten/UMCU/R/general_workflow/Kidney_Dataset/")
To locate a specific file path replace the above line with datadir
<- file.path(“~/Folder/SubFolder/DataLocation”) replace the Folder,
SubFolder, DataLocation as needed. The DataLocation folder should
contain a dccs, pkcs, and annotation folder with each set of files
present as needed automatically list files in each directory for
use.
Take care you import a column with nuclei count separately if
you want.
DCCFiles <- dir(file.path(datadir, "dccs"), pattern = ".dcc$",
full.names = TRUE, recursive = TRUE)
PKCFiles <- dir(file.path(datadir, "pkcs"), pattern = ".pkc$",
full.names = TRUE, recursive = TRUE)
SampleAnnotationFile <-
dir(file.path(datadir, "annotation"), pattern = "^[^~]",
full.names = TRUE, recursive = TRUE)
2 load data
Data <-
readNanoStringGeoMxSet(dccFiles = DCCFiles,
pkcFiles = PKCFiles,
phenoDataFile = SampleAnnotationFile,
phenoDataSheet = "Template",
phenoDataDccColName = "Sample_ID",
protocolDataColNames = c("aoi", "roi"),
experimentDataColNames = c("panel"))
#save data to prevent loading time for retakes
saveData<-Data
#Data<-saveData
#change Data column names and manual correction of spelling errors
Data@phenoData@data[["slide_name"]]<-Data@phenoData@data[["slide name"]]
Data@phenoData@data[["slide name"]]<- NULL
Data@phenoData@data[["ANN4"]]<-gsub("i", "", Data@phenoData@data[["ANN4"]])
#+1 references the slide name column
ann_size<-length(colnames(Data@phenoData@data)[grepl("ANN",colnames(Data@phenoData@data))])+1
ann_names<-c(colnames(Data@phenoData@data)[grepl("ANN",colnames(Data@phenoData@data))],"slide_name")
# Feel free to change the order of which colors are appointed.
color<-c("#A349A4", "#FFFF33", "#E7298A", "#091833", "#1B9E77", "#D95F02", "#7570B3", "#66A61E", "#E6AB02", "#8DD3C7", "#9F000F", "#BEBADA", "#FB8072", "#80B1D3", "#FDB462", "#B3DE69", "#FCCDE5", "#D9D9D9", "#BC80BD", "#CCEBC5", "#FFED6F", "#377EB8", "#984EA3", "#4DAF4A", "#FF71CE", "#FF7F00", "#A6CEE3", "#1F78B4", "#B2DF8A", "#33A02C", "#FB9A99", "#E31A1C", "#FDBF6F", "#CAB2D6", "#6A3D9A", "#FFFF99", "#B15928")
show_col(color)

# Use count and count_max to set the range of color needed for each column.
# With setNames() the color code is linked to each unique individual value of each column.
count=1
color_list = list()
for (ann in ann_names) {
count_max = count+length(unique(Data@phenoData@data[[ann]]))-1
color_list[[ann]]<-setNames(color[count:count_max], unique(Data@phenoData@data[[ann]]))
count=count_max+1
}
color_list
## $ANN1
## DKD normal
## "#A349A4" "#FFFF33"
##
## $ANN2
## disease3 disease4 normal3 normal4 disease1B disease2B normal2B
## "#E7298A" "#091833" "#1B9E77" "#D95F02" "#7570B3" "#66A61E" "#E6AB02"
##
## $ANN3
## glomerulus tubule
## "#8DD3C7" "#9F000F"
##
## $ANN4
## abnormal NA normal
## "#BEBADA" "#FB8072" "#80B1D3"
##
## $slide_name
## disease3 disease4 normal3 normal4 disease1B disease2B normal2B
## "#FDB462" "#B3DE69" "#FCCDE5" "#D9D9D9" "#BC80BD" "#CCEBC5" "#FFED6F"
paste("Reads from following runs used: ",unique(pData(protocolData(Data))$SeqSetId))
## [1] "Reads from following runs used: "
3 Study design
pkcs <- annotation(Data)
modules <- gsub(".pkc", "", pkcs)
kable(data.frame(PKCs = pkcs, modules = modules))
| TAP_H_WTA_v1.0.pkc |
TAP_H_WTA_v1.0 |
Select the annotations we want to show, use `` to surround column
names with spaces or special symbols
count_mat <- dplyr::count(pData(Data), ANN1,ANN2,ANN3,ANN4,slide_name)
Simplify the slide names if required
count_mat$slide_name <- gsub("disease", "d", gsub("normal", "n", count_mat$slide_name))
count_mat$ANN2 <- gsub("disease", "d", gsub("normal", "n", count_mat$ANN2))
#count_mat$path_ann <- gsub("i", "", count_mat$path_ann) #correcting spelling error
Gather the data and plot in order: class, slide name, region,
segment
test_gr <- gather_set_data(count_mat, 1:ann_size)
test_gr$x <- factor(test_gr$x, levels = ann_names)
Plot Sankey
ggplot(test_gr, height = 10, width = 10, aes(x, id = id, split = y, value = n)) +
geom_parallel_sets(aes(fill = ANN3), alpha = 0.5, axis.width = 0.1) +
geom_parallel_sets_axes(axis.width = 0.2) +
geom_parallel_sets_labels(color = "white", size = 5) +
theme_classic(base_size = 12) +
theme(legend.position = "bottom",
axis.ticks.y = element_blank(),
axis.line = element_blank(),
axis.text.y = element_blank()) +
scale_y_continuous(expand = expansion(0)) +
scale_x_discrete(expand = expansion(0)) +
labs(x = "", y = "") +
scale_fill_manual(values=color_list$ANN3) +
annotate(geom = "segment", x = 4.25, xend = 4.25,
y = 10, yend = 61, lwd = 2) +
annotate(geom = "text", x = 4.19, y = 25, angle = 90, size = 5,
hjust = 0.5, label = "50 segments")

4 QC & Pre-processing
Shift counts to one
#shift any expression counts with a value of 0 to 1 to enable in downstream transformations.
Data <- shiftCountsOne(Data, useDALogic = TRUE)
4.1 Segment QC
We first assess sequencing quality and adequate tissue sampling for
every ROI/AOI segment.
Every ROI/AOI segment will be tested for:
Raw sequencing reads: segments with >1000 raw reads are removed. %
Aligned,% Trimmed, or % Stitched sequencing reads: segments below ~80%
for one or more of these QC parameters are removed. % Sequencing
saturation ([1-deduplicated reads/aligned reads]%): segments below ~50%
require additional sequencing to capture full sample diversity and are
not typically analyzed until improved. Negative Count: this is the
geometric mean of the several unique negative probes in the GeoMx panel
that do not target mRNA and establish the background count level per
segment; segments with low negative counts (1-10) are not necessarily
removed but may be studied closer for low endogenous gene signal and/or
insufficient tissue sampling. No Template Control (NTC) count: values
>1,000 could indicate contamination for the segments associated with
this NTC; however, in cases where the NTC count is between 1,000-
10,000, the segments may be used if the NTC data is uniformly low (e.g.
0-2 counts for all probes). Nuclei: >100 nuclei per segment is
generally recommended; however, this cutoff is highly study/tissue
dependent and may need to be reduced; what is most important is
consistency in the nuclei distribution for segments within the study.
Area: generally correlates with nuclei; a strict cutoff is not generally
applied based on area.
4.1.1 Select Segment QC
First, we select the QC parameter cutoffs, against which our ROI/AOI
segments will be tested and flagged appropriately. We have selected the
appropriate study-specific parameters for this study. Note: the default
QC values recommended above are advised when surveying a new dataset for
the first time.
Default QC cutoffs are commented in () adjacent to the respective
parameters study-specific values were selected after visualizing the QC
results in more detail below
QC_params <-
list(minSegmentReads = 1000, # Minimum number of reads (1000)
percentTrimmed = 80, # Minimum % of reads trimmed (80%)
percentStitched = 80, # Minimum % of reads stitched (80%)
percentAligned = 75, # Minimum % of reads aligned (80%)
percentSaturation = 50, # Minimum sequencing saturation (50%)
minNegativeCount = 1, # Minimum negative control counts (10)
maxNTCCount = 9000, # Maximum counts observed in NTC well (1000)
minNuclei = 20, # Minimum # of nuclei estimated (100)
minArea = 1000) # Minimum segment area (5000)
Data <-
setSegmentQCFlags(Data, qcCutoffs = QC_params)
cat("pre-QC features:", dim(Data)[1], "\npre-QC samples:", dim(Data)[2])
## pre-QC features: 18642
## pre-QC samples: 276
#Table for clarification
QCparams_df <- data.frame (
items = c("minSegmentReads","percentTrimmed","percentStitched","percentAligned","percentSaturation",
"minNegativeCount","maxNTCCount","minNuclei","minArea"),
defaults = c(1000,80,80,80,50,10,1000,100,5000),
actual = c(QC_params$minSegmentReads,QC_params$percentTrimmed,QC_params$percentStitched,QC_params$percentAligned,QC_params$percentSaturation,QC_params$minNegativeCount,QC_params$maxNTCCount,QC_params$minNuclei,QC_params$minArea)
)
datatable(QCparams_df, rownames=FALSE,
caption = "QC thresholds",
extensions = 'Buttons', options = list (
dom = 'Bftrip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
)
)
Collate QC Results
QCResults <- protocolData(Data)[["QCFlags"]]
flag_columns <- colnames(QCResults)
QC_Summary <- data.frame(Pass = colSums(!QCResults[, flag_columns]),
Warning = colSums(QCResults[, flag_columns]))
QCResults$QCStatus <- apply(QCResults, 1L, function(x) {
ifelse(sum(x) == 0L, "PASS", "WARNING")
})
QC_Summary["TOTAL FLAGS", ] <-
c(sum(QCResults[, "QCStatus"] == "PASS"),
sum(QCResults[, "QCStatus"] == "WARNING"))
col_by <- "ANN1"
col_by_plate <- "Plate_ID"
4.2 Graphical summaries of QC
statistics
Use the tab-menu to navigate!
QC_histogram <- function(assay_data = NULL,
annotation = NULL,
fill_by = NULL,
thr = NULL,
scale_trans = NULL) {
plt <- ggplot(assay_data,
aes_string(x = paste0("unlist(`", annotation, "`)"),
fill = fill_by)) +
geom_histogram(bins = 50) +
geom_vline(xintercept = thr, lty = "dashed", color = "black") +
theme_bw() + guides(fill = "none") +
facet_wrap(as.formula(paste("~", fill_by)), nrow = 4) +
scale_fill_manual(values=color_list$ANN1) +
labs(x = annotation, y = "segments, #", title = annotation)
if(!is.null(scale_trans)) {
plt <- plt +
scale_x_continuous(trans = scale_trans)
}
plt
}
Trimmed
QC_histogram(sData(Data), "Trimmed (%)", col_by, QC_params$percentTrimmed)

Stiched (%)
QC_histogram(sData(Data), "Stitched (%)", col_by, QC_params$percentStitched)

Aligned (%)
QC_histogram(sData(Data), "Aligned (%)", col_by,QC_params$percentAligned)

Sequencing Saturation (%)
QC_histogram(sData(Data), "Saturated (%)", col_by, QC_params$percentSaturation) +
labs(title = "Sequencing Saturation (%)",
x = "Sequencing Saturation (%)")

Area
QC_histogram(sData(Data), "area", col_by, QC_params$minArea, scale_trans = "log10")

Nuclei count
QC_histogram(sData(Data), "nuclei", col_by, QC_params$minNuclei)

DuplicationRate
ggplot(pData(protocolData(Data)),
aes(x = Plate_ID, fill=Plate_ID,
y = (DeduplicatedReads/Raw))) +
geom_violin() +
geom_jitter(width = .2) +
labs(y = "Deduplicated / Raw reads") +
scale_y_continuous(labels = scales::percent) +
theme_bw()

# Plate_ID to check the low dub/raw count
QC_histogram(sData(Data), "Saturated (%)", col_by_plate, QC_params$percentSaturation) +
labs(title = "Sequencing Saturation (%)",
x = "Sequencing Saturation (%)")

Negprobes vs Endogenous
tmp_target_Data <- aggregateCounts(Data)
#get negative probe data
negs<-subset(tmp_target_Data,CodeClass=="Negative")
p1<-ggplot(pData(negs),
aes(x = ANN1, fill = ANN1,
y = assayDataElement(negs, elt = "exprs"))) +
geom_violin() +
geom_jitter(width = .2) +
labs(y = "Negative probes Expression") +
scale_y_continuous(limits = c(1,3000), trans = "log2") +
theme_bw()
# get endogenous probe data
end<-subset(tmp_target_Data,CodeClass=="Endogenous")
p2<-ggplot(pData(end),
aes(x = ANN1, fill = ANN1,
y = colMeans(assayDataElement(end, elt = "exprs")))) +
geom_violin() +
geom_jitter(width = .2) +
labs(y = "Endogenous probes Expression (mean)") +
scale_y_continuous(limits = c(1,3000),trans = "log2") +
theme_bw()
pl <-list(p1,p2)
plot_grid(plotlist=pl, nrow=1, align='h')

Neg_probe reads compared to raw_reads
# make background total neg probe count
fdata_df<-fData(Data)
negprobesnames<-rownames(fdata_df[fdata_df$Negative==TRUE,])
temp_exp<-assayDataElement(Data,elt='exprs')
negprobe_expr_fd<-temp_exp[rownames(temp_exp) %in% negprobesnames,]
tot_neg_ctrl_reads<-colSums(negprobe_expr_fd)
tot_dedup_reads<-pData(protocolData(Data))$DeduplicatedReads
df<-data.frame('aoi'= names(tot_neg_ctrl_reads),'tot_dedup_reads' = as.numeric(tot_dedup_reads),'tot_neg_ctrl_reads'=as.numeric(tot_neg_ctrl_reads))
df<-melt(df,id="aoi")
ggplot(df,aes(fill=variable,y=value,x=aoi)) +
geom_bar(position="identity",stat="identity") +
scale_y_continuous(trans = log2_trans()) +
theme(legend.position="bottom",axis.text.x = element_blank(),axis.ticks.x=element_blank())

Duplicated reads vs Background
# get dcc per plate. sum negprobe counts/dcc/plate
ggplot(pData(protocolData(Data)),
aes(x = Plate_ID, fill=Plate_ID,
y = DeduplicatedReads)) +
geom_violin() +
geom_jitter(width = .2) +
labs(y = "Deduplicated / Raw reads") +
scale_y_log10()+
geom_hline(data =pData(protocolData(Data)) ,
aes(yintercept = NTC, colour=Plate_ID)) +
theme_bw()

Duplicated reads vs ROIarea
temp_df<-cbind(pData(Data),pData(protocolData(Data)),dcc=rownames(pData(Data)))
ggplot(temp_df,
aes(x = dcc, colour=slide_name,
y = (DeduplicatedReads/area) )) +
geom_point() +
ylim(0,20) +
labs(y = "Deduplicated reads / ROI area") +
theme(axis.text.x = element_text(size =6, angle=90, hjust=1) )

Duplicated reads vs nuclei
temp_df<-cbind(pData(Data),pData(protocolData(Data)),dcc=rownames(pData(Data)))
ggplot(temp_df,
aes(x = dcc, colour=slide_name,
y = (DeduplicatedReads/nuclei) )) +
geom_point() +
ylim(0,200) +
labs(y = "Deduplicated reads / nuclei") +
theme(axis.text.x = element_text(size =6, angle=90, hjust=1) )

4.3 Process Negative GeoMeans
# Calculate the negative geometric means for each module
# It will show only the negative probes geomean, so expect less segments.
negativeGeoMeans <-
esBy(negativeControlSubset(Data),
GROUP = "Module",
FUN = function(x) {
assayDataApply(x, MARGIN = 2, FUN = ngeoMean, elt = "exprs")
})
protocolData(Data)[["NegGeoMean"]] <- negativeGeoMeans
negCols <- paste0("NegGeoMean_", modules)
pData(Data)[, negCols] <- sData(Data)[["NegGeoMean"]]
for(ann in negCols) {
plt <- QC_histogram(pData(Data), ann, col_by, 2, scale_trans = "log10")
print(plt)
}

# Detatch neg_geomean columns ahead of aggregateCounts call
pData(Data) <- pData(Data)[, !colnames(pData(Data)) %in% negCols]
Show all NTC values, Freq = # of Segments with a given NTC count:
QC<-sData(Data)
ntc_df <-QC[,c("slide_name","Plate_ID","NTC_ID","NTC")]
temptable<-ntc_df %>% dplyr::count(ntc_df$slide_name,ntc_df$NTC_ID,ntc_df$Plate_ID,ntc_df$NTC)
colnames(temptable) <- c("Slide_name","NTC_ID","Plate_ID","NTC_count","Number_of_samples")
datatable(temptable, rownames = FALSE)
kable(table(NTC_Count = sData(Data)$NTC), col.names = c("NTC Count", "# of Segments"))
| 3 |
64 |
| 113 |
71 |
| 397 |
47 |
| 8704 |
94 |
kable(QC_Summary, caption = "QC Summary Table for each Segment")
QC Summary Table for each Segment
| LowReads |
272 |
4 |
| LowTrimmed |
276 |
0 |
| LowStitched |
273 |
3 |
| LowAligned |
266 |
10 |
| LowSaturation |
272 |
4 |
| LowNegatives |
276 |
0 |
| HighNTC |
276 |
0 |
| LowNuclei |
276 |
0 |
| LowArea |
265 |
11 |
| TOTAL FLAGS |
259 |
17 |
datatable(QC_Summary,
caption = "AOI QC Summary",
extensions = 'Buttons', options = list (
dom = 'Bftrip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
)
)
Show AOIs which fail critical QCs.
QC<-sData(Data)
undersat<-subset(QC, `Saturated (%)`<= QC_params$percentSaturation)
if(nrow(undersat)> 0) {
datatable(aggregate(undersat, by=list(undersat$SampleID),paste,collapse=";"),
extensions = 'Buttons', options = list (
dom = 'Bftrip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
)
)}
Subsetting our dataset has removed samples which did not pass QC
Data <- Data[, QCResults$QCStatus == "PASS"]
Generally keep the qcCutoffs parameters unchanged. Set
removeLocalOutliers to FALSE if you do not want to remove local
outliers
Data <- setBioProbeQCFlags(Data,
qcCutoffs = list(minProbeRatio = 0.1,
percentFailGrubbs = 20),
removeLocalOutliers = FALSE)
ProbeQCResults <- fData(Data)[["QCFlags"]]
Define QC table for Probe QC
qc_df <- data.frame(Passed = sum(rowSums(ProbeQCResults[, -1]) == 0),
Global = sum(ProbeQCResults$GlobalGrubbsOutlier),
Local = sum(rowSums(ProbeQCResults[, -2:-1]) > 0
& !ProbeQCResults$GlobalGrubbsOutlier))
Subset object to exclude all that did not pass Ratio & Global
testing
ProbeQCPassed <-
subset(Data,
fData(Data)[["QCFlags"]][,c("LowProbeRatio")] == FALSE &
fData(Data)[["QCFlags"]][,c("GlobalGrubbsOutlier")] == FALSE)
Data <- ProbeQCPassed
cat("After QC features:", dim(Data)[1], "\nAfter QC samples:", dim(Data)[2])
## After QC features: 18641
## After QC samples: 259
Check how many unique targets the object has
length(unique(featureData(Data)[["TargetName"]]))
## [1] 18504
Collapse to targets
target_Data <- aggregateCounts(Data)
exprs(target_Data)[1:5, 1:2]
## DSP-1001250007851-H-A02.dcc DSP-1001250007851-H-A03.dcc
## A2M 485 262
## NAT2 15 18
## ACADM 31 15
## ACADS 27 17
## ACAT1 29 24
Define LOQ SD threshold and minimum value
cutoff <- 2
minLOQ <- 2
4.4 Limit of Quantification
We define a limit of quantification (LOQ) per ROI/AOI segment based
on the negative control probes to guide the filtering of segments and
genes with low signal relative to background. The formula for
calculating the LOQ in the \(i^{th}\)
segment at \(n\) standard deviations
(\(n = 2\) for this study) is: \(LOQ_i=geomean(NegProbe_i)*geoSD(NegProbe_i)^n\)
Calculate LOQ per module tested
LOQ <- data.frame(row.names = colnames(target_Data))
for(module in modules) {
vars <- paste0(c("NegGeoMean_", "NegGeoSD_"),
module)
if(all(vars[1:2] %in% colnames(pData(target_Data)))) {
LOQ[, module] <-
pmax(minLOQ,
pData(target_Data)[, vars[1]] *
pData(target_Data)[, vars[2]] ^ cutoff)
}
}
pData(target_Data)$LOQ <- LOQ
4.5 Filtering
After determining the limit of quantification (LOQ) per segment, we
recommend filtering out either segments and/or genes with abnormally low
signal. Filtering is an important step to focus on the true biological
data of interest.
We determine the number of genes detected in each segment across the
dataset.
LOQ_Mat <- c()
for(module in modules) {
ind <- fData(target_Data)$Module == module
Mat_i <- t(esApply(target_Data[ind, ], MARGIN = 1,
FUN = function(x) {
x > LOQ[, module]
}))
LOQ_Mat <- rbind(LOQ_Mat, Mat_i)
}
# ensure ordering since this is stored outside of the geomxSet
LOQ_Mat <- LOQ_Mat[fData(target_Data)$TargetName, ]
4.5.1 Segment Gene Detection
We first filter out segments with exceptionally low signal. These
segments will have a small fraction of panel genes detected above the
LOQ relative to the other segments in the study. Let’s visualize the
distribution of segments with respect to their % genes detected:
Save detection rate information to pheno data
pData(target_Data)$GenesDetected <-
colSums(LOQ_Mat, na.rm = TRUE)
pData(target_Data)$GeneDetectionRate <-
pData(target_Data)$GenesDetected / nrow(target_Data)
Determine detection thresholds: 1%, 5%, 10%, 15%, >15%
pData(target_Data)$DetectionThreshold <-
cut(pData(target_Data)$GeneDetectionRate,
breaks = c(0, 0.01, 0.05, 0.1, 0.15, 0.2,1),
labels = c("<1%", "1-5%", "5-10%", "10-15%", "15-20%", ">20%"))
# stacked bar plot of different cut points (1%, 5%, 10%, 15%)
ggplot(pData(target_Data),
aes(x = DetectionThreshold)) +
geom_bar(aes(fill = ANN1)) +
geom_text(stat = "count", aes(label = ..count..), vjust = -0.5) +
theme_bw() +
scale_y_continuous(expand = expansion(mult = c(0, 0.1))) +
scale_fill_manual(values=color_list$ANN1) +
labs(x = "Gene Detection Rate",
y = "Segments, #",
fill = "Segment Type")

cut percent genes detected at 1, 5, 10, 15
kable(table(pData(target_Data)$DetectionThreshold,
pData(target_Data)$ANN1))
| <1% |
0 |
1 |
| 1-5% |
1 |
6 |
| 5-10% |
12 |
6 |
| 10-15% |
28 |
5 |
| 15-20% |
22 |
15 |
| >20% |
84 |
79 |
# set threshold for detectionlevel
# default 0.1
gene_det_threshold <- 0.05
target_Data <-
target_Data[, pData(target_Data)$GeneDetectionRate >= gene_det_threshold]
dim(target_Data)
## Features Samples
## 18504 251
4.5.2 collect annotations
# **Select the annotations we want to show, use `` to surround column names with spaces or special symbols**
old_count_mat<-count_mat
count_mat <- dplyr::count(pData(Data), ANN1,ANN2,ANN3,ANN4,slide_name)
# simplify_slide_names if needed
#count_mat$slide_name <- gsub("disease", "d", gsub("normal", "n", count_mat$slide_name))
# gather the data and plot in order: class, slide name, region, segment
test_gr <- gather_set_data(count_mat, 1:ann_size)
test_gr$x <-
factor(test_gr$x,
levels = ann_names)
aoilist <-names(as.data.frame(assayDataElement(target_Data, elt= "exprs")))
ANN1 <-as.data.frame(pData(target_Data)$ANN1,unique(count_mat$ANN1))
colnames(ANN1) <- "class"
row.names(ANN1) <-aoilist
ANN2 <-as.data.frame(pData(target_Data)$ANN2,unique(count_mat$ANN2))
colnames(ANN2) <- "slide_ann"
row.names(ANN2) <-aoilist
ANN3 <-as.data.frame(pData(target_Data)$ANN3,unique(count_mat$ANN3))
colnames(ANN3) <- "region"
row.names(ANN3) <-aoilist
ANN4 <-as.data.frame(pData(target_Data)$ANN4,unique(count_mat$ANN4))
colnames(ANN4) <- "path"
row.names(ANN4) <-aoilist
SN <-as.data.frame(pData(target_Data)$slide_name, unique(count_mat$slide_name))
colnames(SN) <- "slide_name"
row.names(SN) <-aoilist
ann<-cbind(ANN1,ANN2,ANN3,ANN4,SN)
4.6 Manual removal of samples/classes
active_aois<-rownames(ann)
re-Collect annotations
# gather the data and plot in order: class, slide name, region, segment
test_gr <- gather_set_data(count_mat, 1:ann_size)
test_gr$x <-
factor(test_gr$x,
levels = ann_names)
re-Plot Sankey
ggplot(test_gr, aes(x, id = id, split = y, value = n)) +
geom_parallel_sets(aes(fill = ANN1), alpha = 0.5, axis.width = 0.1) +
geom_parallel_sets_axes(axis.width = 0.2) +
geom_parallel_sets_labels(color = "white", size = 5) +
theme_classic(base_size = 17) +
theme(legend.position = "bottom",
axis.ticks.y = element_blank(),
axis.line = element_blank(),
axis.text.y = element_blank()) +
scale_y_continuous(expand = expansion(0)) +
scale_x_discrete(expand = expansion(0)) +
scale_fill_manual(values=color_list$ANN1) +
labs(x = "", y = "") +
annotate(geom = "segment", x = 3.25, xend = 3.25, y = 10,
yend = 60, lwd = 2) +
annotate(geom = "text", x = 3.19, y = 25, angle = 90, size = 5,
hjust = 0.5, label = "50 segments")

4.7 Gene Detection Rate
Calculate detection rate
LOQ_Mat <- LOQ_Mat[, colnames(target_Data)]
fData(target_Data)$DetectedSegments <- rowSums(LOQ_Mat, na.rm = TRUE)
fData(target_Data)$DetectionRate <-
fData(target_Data)$DetectedSegments / nrow(pData(target_Data))
Gene of interest detection table
goi <- c("PDCD1", "CD274", "IFNG", "CD8A", "CD68", "EPCAM",
"KRT18", "NPHS1", "NPHS2", "CALB1", "CLDN8")
goi_df <- data.frame(
Gene = goi,
Number = fData(target_Data)[goi, "DetectedSegments"],
DetectionRate = percent(fData(target_Data)[goi, "DetectionRate"]))
4.8 Gene Filtering
We will graph the total number of genes detected in different
percentages of segments. Based on the visualization below, we can better
understand global gene detection in our study and select how many low
detected genes to filter out of the dataset. Gene filtering increases
performance of downstream statistical tests and improves interpretation
of true biological signal.
Plot detection rate
plot_detect <- data.frame(Freq = c(1, 5, 10, 20, 30, 50))
plot_detect$Number <-
unlist(lapply(c(0.01, 0.05, 0.1, 0.2, 0.3, 0.5),
function(x) {sum(fData(target_Data)$DetectionRate >= x)}))
plot_detect$Rate <- plot_detect$Number / nrow(fData(target_Data))
rownames(plot_detect) <- plot_detect$Freq
ggplot(plot_detect, aes(x = as.factor(Freq), y = Rate, fill = Rate)) +
geom_bar(stat = "identity") +
geom_text(aes(label = formatC(Number, format = "d", big.mark = ",")),
vjust = 1.6, color = "black", size = 4) +
scale_fill_gradient2(low = "orange2", mid = "lightblue",
high = "dodgerblue3", midpoint = 0.65,
limits = c(0,1),
labels = scales::percent) +
theme_bw() +
scale_y_continuous(labels = scales::percent, limits = c(0,1),
expand = expansion(mult = c(0, 0))) +
labs(x = "% of Segments",
y = "Genes Detected, % of Panel > LOQ")

Subset to target genes detected in at least 10% of the samples. Also
manually include the negative control probe, for downstream use
# default=0.1
negativeProbefData <- subset(fData(target_Data), CodeClass == "Negative")
neg_probes <- unique(negativeProbefData$TargetName)
target_Data <-
target_Data[fData(target_Data)$DetectionRate >= 0.05 |
fData(target_Data)$TargetName %in% neg_probes, ]
# retain only detected genes of interest
goi <- goi[goi %in% rownames(target_Data)]
5 Normalization
We will now normalize the GeoMx data for downstream visualizations
and differential expression. The two common methods for normalization of
DSP-NGS RNA data are i) quartile 3 (Q3) or ii) background
normalization.
Both of these normalization methods estimate a normalization factor
per segment to bring the segment data distributions together. More
advanced methods for normalization and modeling are under active
development. However, for most studies, these methods are sufficient for
understanding differences between biological classes of segments and
samples.
Q3 normalization is typically the preferred normalization strategy
for most DSP-NGS RNA studies. Given the low negative probe counts in
this particular dataset as shown during Segment QC, we would further
avoid background normalization as it may be less stable.
Before normalization, we will explore the relationship between the
upper quartile (Q3) of the counts in each segment with the geometric
mean of the negative control probes in the data. Ideally, there should
be a separation between these two values to ensure we have stable
measure of Q3 signal. If you do not see sufficient separation between
these values, you may consider more aggressive filtering of low signal
segments/genes.
Graph Q3 value vs negGeoMean of Negatives
ann_of_interest <- "ANN3"
Stat_data <-
data.frame(row.names = colnames(exprs(target_Data)),
Segment = colnames(exprs(target_Data)),
Annotation = pData(target_Data)[, ann_of_interest],
Q3 = unlist(apply(exprs(target_Data), 2,
quantile, 0.75, na.rm = TRUE)),
NegProbe = exprs(target_Data)[neg_probes, ])
Stat_data_m <- melt(Stat_data, measure.vars = c("Q3", "NegProbe"),
variable.name = "Statistic", value.name = "Value")
plt1 <- ggplot(Stat_data_m,
aes(x = Value, fill = Statistic)) +
geom_histogram(bins = 40) + theme_bw() +
scale_x_continuous(trans = "log2") +
facet_wrap(~Annotation, nrow = 1) +
scale_fill_brewer(palette = 3, type = "qual") +
labs(x = "Counts", y = "Segments, #")
plt2 <- ggplot(Stat_data,
aes(x = NegProbe, y = Q3, color = Annotation)) +
geom_abline(intercept = 0, slope = 1, lty = "dashed", color = "darkgray") +
geom_point() + guides(color = "none") + theme_bw() +
scale_x_continuous(trans = "log2") +
scale_y_continuous(trans = "log2") +
theme(aspect.ratio = 1) +
labs(x = "Negative Probe GeoMean, Counts", y = "Q3 Value, Counts")
plt3 <- ggplot(Stat_data,
aes(x = NegProbe, y = Q3 / NegProbe, color = Annotation)) +
geom_hline(yintercept = 1, lty = "dashed", color = "darkgray") +
geom_point() + theme_bw() +
scale_x_continuous(trans = "log2") +
scale_y_continuous(trans = "log2") +
theme(aspect.ratio = 1) +
labs(x = "Negative Probe GeoMean, Counts", y = "Q3/NegProbe Value, Counts")
btm_row <- plot_grid(plt2, plt3, nrow = 1, labels = c("B", ""),
rel_widths = c(0.43,0.57))
plot_grid(plt1, btm_row, ncol = 1, labels = c("A", ""))

Q3 norm (75th percentile) for WTA/CTA with or without custom
spike-ins
target_Data <- normalize(target_Data ,
norm_method = "quant",
desiredQuantile = .75,
toElt = "q_norm")
#, data_type = "RNA" depricated after 4.1
Background normalization for WTA/CTA without custom spike-in
target_Data <- normalize(target_Data,
norm_method = "neg",
fromElt = "exprs",
toElt = "neg_norm")
# , data_type = "RNA" depricated after 4.1
5.1 Visualize the first 10 segments with
each normalization method
Use the tab-menu to navigate!
#Fix zero values, which go to -inf in log transform in standard boxplot
# temp <-as.matrix(exprs((target_Data)[,1:10]))
# long <- melt(temp)
# colnames(long) <- c("gene","segment","count")
# ggplot(long, aes(x=segment,y=count)) +
# geom_boxplot(fill="#9EDAE5") +
# scale_y_continuous(trans=scales::pseudo_log_trans(base = 10)) +
# scale_x_discrete(labels=c(1:10)) +
# labs(title="Raw counts", x="segment", y = "Counts, Raw")
#
#
# temp <-as.matrix(assayDataElement(target_Data[,1:10], elt = "q_norm"))
# long <- melt(temp)
# colnames(long) <- c("gene","segment","count")
# ggplot(long, aes(x=segment,y=count)) +
# geom_boxplot(fill = "#2CA02C") +
# scale_y_continuous(trans=scales::pseudo_log_trans(base = 10)) +
# scale_x_discrete(labels=c(1:10)) +
# labs(title="Q3 Norm Counts", x="segment", y = "Counts, Q3 Normalized")
#
#
# temp <-as.matrix(assayDataElement(target_Data[,1:10], elt = "neg_norm"))
# long <- melt(temp)
# colnames(long) <- c("gene","segment","count")
# ggplot(long, aes(x=segment,y=count)) +
# geom_boxplot(fill = "#FF7F0E") +
# scale_y_continuous(trans=scales::pseudo_log_trans(base = 10)) +
# scale_x_discrete(labels=c(1:10)) +
# labs(title="Neg Norm Counts", x="segment", y = "Counts, Neg. Normalized")
raw counts
boxplot(exprs(target_Data)[,1:8],
col = "#9EDAE5", main = "Raw Counts",
log = "y", names = 1:8, xlab = "Segment",
ylab = "Counts, Raw")

Q3 normalized
boxplot(assayDataElement(target_Data[,1:8], elt = "q_norm"),
col = "#2CA02C", main = "Q3 Norm Counts",
log = "y", names = 1:8, xlab = "Segment",
ylab = "Counts, Q3 Normalized")

Negative probe normalization
boxplot(assayDataElement(target_Data[,1:8], elt = "neg_norm"),
col = "#FF7F0E", main = "Neg Norm Counts",
log = "y", names = 1:8, xlab = "Segment",
ylab = "Counts, Neg. Normalized")

6 Unsupervised Analysis
6.1 UMAP
Use the tab-menu to navigate!
1
gc()
## used (Mb) gc trigger (Mb) max used (Mb)
## Ncells 10406983 555.8 28883698 1542.6 28883698 1542.6
## Vcells 69398283 529.5 114624478 874.6 114622516 874.6
custom_umap <- umap::umap.defaults
custom_umap$random_state <- 42
# run UMAP
umap_out <-
umap(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
config = custom_umap)
pData(target_Data)[, c("UMAP1", "UMAP2")] <- umap_out$layout[, c(1,2)]
ggplot(pData(target_Data),
aes(x = UMAP1, y = UMAP2, color = ANN1, shape = slide_name)) +
geom_point(size = 3) +
#geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
theme_bw()

2
umap_out <-
umap(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
config = custom_umap)
pData(target_Data)[, c("UMAP1", "UMAP2")] <- umap_out$layout[, c(1,2)]
ggplot(pData(target_Data),
aes(x = UMAP1, y = UMAP2, color = ANN2, shape = ANN1)) +
geom_point(size = 3) +
#geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
theme_bw()

3
umap_out <-
umap(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
config = custom_umap)
pData(target_Data)[, c("UMAP1", "UMAP2")] <- umap_out$layout[, c(1,2)]
ggplot(pData(target_Data),
aes(x = UMAP1, y = UMAP2, color = ANN3, shape = ANN2)) +
geom_point(size = 3) +
#geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
theme_bw()

6.1 Run tSNE
Use the tab-menu to navigate!
One common approach to understanding high-plex data is dimension
reduction. Two common methods are UMAP and tSNE, which are
non-orthogonally constrained projections that cluster samples based on
overall gene expression. In this study, we see by either UMAP (from the
umap package) or tSNE (from the Rtsne package)
1
set.seed(42) # set the seed for tSNE as well
tsne_out <-
Rtsne(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
perplexity = ncol(target_Data)*.15)
pData(target_Data)[, c("tSNE1", "tSNE2")] <- tsne_out$Y[, c(1,2)]
ggplot(pData(target_Data),
aes(x = tSNE1, y = tSNE2, shape = ANN3, color = ANN1)) +
geom_point(size = 3) +
geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
theme_bw()

2
tsne_out <-
Rtsne(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
perplexity = ncol(target_Data)*.15)
pData(target_Data)[, c("tSNE1", "tSNE2")] <- tsne_out$Y[, c(1,2)]
ggplot(pData(target_Data),
aes(x = tSNE1, y = tSNE2, color = ANN2, shape = ANN1)) +
geom_point(size = 3) +
#geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
theme_bw()

3
tsne_out <-
Rtsne(t(log2(assayDataElement(target_Data , elt = "q_norm"))),
perplexity = ncol(target_Data)*.15)
pData(target_Data)[, c("tSNE1", "tSNE2")] <- tsne_out$Y[, c(1,2)]
ggplot(pData(target_Data),
aes(x = tSNE1, y = tSNE2, color = ANN3, shape = ANN2)) +
geom_point(size = 3) +
#geom_text_repel(aes(label=row.names(pData(target_Data))), size=2,max.overlaps = 100)+
theme_bw()

6.2 Clustering high CV Genes
Another approach to explore the data is to calculate the coefficient
of variation (\(CV\)) for each gene
(\(g\)) using the formula \(CV_g=SD_g/mean_g\). We then identify genes
with high CVs that should have large differences across the various
profiled segments. This unbiased approach can reveal highly variable
genes across the study.
We plot the results using unsupervised hierarchical clustering,
displayed as a heatmap.
# create a log2 transform of the data for analysis
assayDataElement(object = target_Data, elt = "log_q") <-
assayDataApply(target_Data, 2, FUN = log, base = 2, elt = "q_norm")
# create CV function
calc_CV <- function(x) {sd(x) / mean(x)}
CV_dat <- assayDataApply(target_Data,
elt = "log_q", MARGIN = 1, calc_CV)
# show the highest CD genes and their CV values
sort(CV_dat, decreasing = TRUE)[1:5]
## CAMK2N1 AKR1C1 AQP2 REN GDF15
## 0.6344192 0.5238995 0.4752667 0.4414027 0.4276390
Table of CV values
# show the highest CD genes and their CV values
datatable(as.data.frame(CV_dat),
extensions = 'Buttons', options = list (
order = list(1, 'desc'),
dom = 'Bftrip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
), caption = "CV values of genes"
) %>% formatRound(columns=c("CV_dat"), digits=3)
Heatmap genes in the top 3rd of the CV values
GOI <- names(CV_dat)[CV_dat > quantile(CV_dat, 0.75)]
pheatmap(assayDataElement(target_Data[GOI, ], elt = "log_q"),
scale = "row",
cutree_cols = 3,
cutree_rows = 3,
show_rownames = FALSE, show_colnames = TRUE,
border_color = NA,
drop_levels = TRUE,
clustering_method = "average",
clustering_distance_rows = "correlation",
clustering_distance_cols = "correlation",
breaks = seq(-3, 3, 0.05),
color = colorRampPalette(c("purple3", "black", "yellow2"))(120),
annotation_colors = color_list,
annotation_col = pData(target_Data)[, ann_names])

assayDataElement(object = target_Data, elt = "log_q") <- assayDataApply(target_Data, 2, FUN = log, base = 2, elt = "q_norm")
log_q <-as.data.frame(assayDataElement(target_Data, elt= "log_q"))
6.2.0 Create subset of data
# determine AOIs to use
#active_aois<-rownames(ann)[ann$patient=="p4"]
active_aois<-rownames(ann)
6.2.1 Clustering high CV genes for
subset
Calculating CV values
# create a log2 transform of the data for analysis
assayDataElement(object = target_Data, elt = "log_q") <-
assayDataApply(target_Data, 2, FUN = log, base = 2, elt = "q_norm")
# create CV function
calc_CV <- function(x) {sd(x) / mean(x)}
CV_dat <- assayDataApply(target_Data[,active_aois],
elt = "log_q", MARGIN = 1, calc_CV)
Table of CV values
# show the highest CD genes and their CV values
datatable(as.data.frame(CV_dat),
extensions = 'Buttons', options = list (
order = list(1, 'desc'),
dom = 'Bftrip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
), caption = "CV values of genes"
) %>% formatRound(columns=c("CV_dat"), digits=3)
Heatmap on of subset, genes in the top 3rd of the CV
values
# Identify genes in the top 3rd of the CV values
GOI <- names(CV_dat)[CV_dat > quantile(CV_dat, 0.75)]
pheatmap(assayDataElement(target_Data[GOI,active_aois ], elt = "log_q"),
scale = "row",
fontsize_row = 5,
cutree_cols = 3,
cutree_rows = 3,
show_rownames = FALSE, show_colnames = TRUE,
border_color = NA,
clustering_method = "average",
clustering_distance_rows = "correlation",
clustering_distance_cols = "correlation",
breaks = seq(-3, 3, 0.05),
color = colorRampPalette(c("purple3", "black", "yellow2"))(120),
annotation_colors = color_list,
annotation_col =
pData(target_Data)[, ann_names])

7.1 Differential Expression
t-test
gc()
## used (Mb) gc trigger (Mb) max used (Mb)
## Ncells 10444514 557.8 136699932 7300.6 139644550 7457.9
## Vcells 78815981 601.4 278361925 2123.8 360660679 2751.7
plots<-list()
tables<-list()
labels<-list()
test<-"ttest"
mtc<-"BY"
#options: "holm" "hochberg" "hommel" "bonferroni" "BH" "BY" "fdr"
counter=1
comps_df<-data.frame(comp='',val='')
for (class in c("DKD","normal")) {
for (active_group1 in unique(ann$region)) {
for (active_group2 in unique(ann$region)) {
#supress reduncant compares
if(active_group1==active_group2) {next}
comp<-paste(sort(c(class, active_group1,active_group2)),collapse = "_")
if(comp %in% comps_df$comp) {next}
temp_df<-data.frame(comp=comp ,val=1)
comps_df<-rbind(comps_df,temp_df)
labels[[counter]]<-paste(active_group1," vs ", active_group2)
group1<-log_q[,rownames(ann)[ann$class == class & ann$region==active_group1]]
group2<-log_q[,rownames(ann)[ann$class == class & ann$region==active_group2]]
#run t_tests
results<-as.data.frame ( apply(log_q, 1, function(x) t.test(x[colnames(group1)],x[colnames(group2)])$p.value) )
colnames(results)<-"raw_p_value"
#multiple_testing_correction
adj_p_value<- p.adjust(results$raw_p_value,method=mtc)
results<-cbind(results,adj_p_value)
#calc_fdr
FDR<- p.adjust(results$raw_p_value,method="fdr")
results<-cbind(results,FDR)
#fold_changes
#as base data is already log transformed, means need to be subtracted to get FC in log space
fchanges<-as.data.frame(apply(log_q, 1, function(x) (mean(x[colnames(group1)]) - mean(x[colnames(group2)]))))
colnames(fchanges)<-"FC"
#paste("FC",active_group1," / ",active_group2)
results<-cbind(results,fchanges)
#add genenames
results$Gene<-rownames(results)
#set categories based on P-value & FDR for plotting
results$Color <- "NS or FC < 0.5"
results$Color[results$adj_p_value < 0.05] <- "P < 0.05"
results$Color[results$FDR < 0.05] <- "FDR < 0.05"
results$Color[results$FDR < 0.001] <- "FDR < 0.001"
results$Color[abs(results$FC) < 1] <- "NS or FC < 1"
results$Color <- factor(results$Color,
levels = c("NS or FC < 1", "P < 0.05", "FDR < 0.05", "FDR < 0.001"))
#vulcanoplot
# pick top genes for either side of volcano to label
# order genes for convenience:
results$invert_P <- (-log10(results$adj_p_value)) * sign(results$FC)
top_g <- c()
top_g <- c(top_g,
results[ind, 'Gene'][
order(results[ind, 'invert_P'], decreasing = TRUE)[1:15]],
results[ind, 'Gene'][order(results[ind, 'invert_P'], decreasing = FALSE)[1:15]])
top_g<- unique(top_g)
results <- results[, -1*ncol(results)] # remove invert_P from matrix
# Graph results
plots[[counter]]<- ggplot(results,
aes(x = FC, y = -log10(adj_p_value),
color = Color, label = Gene)) +
geom_vline(xintercept = c(1, -1), lty = "dashed") +
geom_hline(yintercept = -log10(0.05), lty = "dashed") +
geom_point() +
labs(x = paste("Enriched in", active_group2," <- log2(FC) -> Enriched in", active_group1),
y = "Significance, -log10(P)",
color = "Significance") +
scale_color_manual(values = c(`FDR < 0.001` = "dodgerblue",
`FDR < 0.05` = "lightblue",
`P < 0.05` = "orange2",
`NS or FC < 0.5` = "gray"),
guide = guide_legend(override.aes = list(size = 4))) +
scale_y_continuous(expand = expansion(mult = c(0,0.05))) +
geom_text_repel(data = subset(results, FDR<0.001 & (-1>FC| FC>1)),
point.padding = 0.15, color = "black", size=3.5,
min.segment.length = .1, box.padding = .2, lwd = 2,
max.overlaps = 50) +
theme_bw(base_size = 20) +
theme(legend.position = "bottom") +
ggtitle(paste("class: ", class," - ",test, mtc,"multitest corr"))
#store tables for display later
tables[[counter]]<-results
counter = counter+1
#datatable(subset(results, Gene %in% GOI), rownames=FALSE,caption = paste("DE results ", active_group1," vs ", active_group2))
}
}
}
grid.arrange(grobs=plots,ncol=2)

#strangly does not appear in html output??
for (c in (2:counter-1)) {
#Gene %in% GOI
print(datatable( subset(tables[[c]], Color == "FDR < 0.001" ),
rownames=FALSE,
extensions = 'Buttons', options = list (
dom = 'Bftrip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
),
caption = paste("DE results ", labels[[1]]),filter='top') %>% formatRound(columns=c("raw_p_value","adj_p_value","FDR","FC"), digits=3))
cat('\n\n<!-- -->\n\n')
}
7.2 DE analysis with LMM
A common statistical approach is to use a linear mixed-effect model
(LMM). The LMM allows the user to account for the subsampling per
tissue; in other words, we adjust for the fact that the multiple regions
of interest placed per tissue section are not independent observations,
as is the assumption with other traditional statistical tests. The
formulation of the LMM model depends on the scientific question being
asked.
Overall, there are two flavors of the LMM model when used with GeoMx
data: i) with and ii) without random slope.
When comparing features that co-exist in a given tissue section, a
random slope is included in the LMM model. When comparing features that
are mutually exclusive in a given tissue section the LMM model does not
require a random slope.
Mostly, we use patient/sample as Random Intercept when they are
combined on slides.

glomerulus - tubule
# convert test variables to factors
pData(target_Data)$testRegion <-
factor(pData(target_Data)$region, c("glomerulus", "tubule"))
pData(target_Data)[["slide"]]<-factor(pData(target_Data)[["slide_name"]])
assayDataElement(object = target_Data, elt = "log_q") <- assayDataApply(target_Data, 2, FUN = log, base = 2, elt = "q_norm")
lmm_results <- c()
mixedOutmc <-
mixedModelDE(target_Data,
elt = "log_q",
modelFormula = ~ testRegion + (1 + testRegion | slide),
groupVar = "testRegion",
nCores = parallel::detectCores(),
multiCore = FALSE)
# format results as data.frame
r_test <- do.call(rbind, mixedOutmc["lsmeans", ])
tests <- rownames(r_test)
r_test <- as.data.frame(r_test)
r_test$Contrast <- tests
# use lapply in case you have multiple levels of your test factor to
# correctly associate gene name with it's row in the results table
r_test$Gene <-
unlist(lapply(colnames(mixedOutmc),
rep, nrow(mixedOutmc["lsmeans", ][[1]])))
r_test$FDR <- p.adjust(r_test$`Pr(>|t|)`, method = "fdr")
r_test <- r_test[, c("Gene", "Contrast", "Estimate", "Pr(>|t|)", "FDR")]
lmm_results <- rbind(lmm_results, r_test)
#subset(lmm_results, Gene %in% GOI)
datatable(lmm_results, rownames = FALSE,
extensions = 'Buttons', options = list (
dom = 'Bftrip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
),
caption = "DE results for Genes of Interest (>75% CV)",filter='top') %>% formatRound(columns=c("Estimate","Pr(>|t|)","FDR"), digits=3)
DKD - normal
# convert test variables to factors
pData(target_Data)$testClass <-
factor(pData(target_Data)$class, c("DKD", "normal"))
pData(target_Data)[["slide"]]<-factor(pData(target_Data)[["slide_name"]])
assayDataElement(object = target_Data, elt = "log_q") <- assayDataApply(target_Data, 2, FUN = log, base = 2, elt = "q_norm")
lmm_results_d <- c()
mixedOutmc <-
mixedModelDE(target_Data,
elt = "log_q",
modelFormula = ~ testClass + (1 | slide),
groupVar = "testClass",
nCores = parallel::detectCores(),
multiCore = FALSE)
# format results as data.frame
r_test <- do.call(rbind, mixedOutmc["lsmeans", ])
tests <- rownames(r_test)
r_test <- as.data.frame(r_test)
r_test$Contrast <- tests
# use lapply in case you have multiple levels of your test factor to
# correctly associate gene name with it's row in the results table
r_test$Gene <-
unlist(lapply(colnames(mixedOutmc),
rep, nrow(mixedOutmc["lsmeans", ][[1]])))
r_test$FDR <- p.adjust(r_test$`Pr(>|t|)`, method = "fdr")
r_test <- r_test[, c("Gene", "Contrast", "Estimate", "Pr(>|t|)", "FDR")]
lmm_results_d <- rbind(lmm_results_d, r_test)
#subset(lmm_results, Gene %in% GOI)
datatable(lmm_results_d, rownames = FALSE,
extensions = 'Buttons', options = list (
dom = 'Bftrip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
),
caption = "DE results for Genes of Interest (>75% CV)",filter='top') %>% formatRound(columns=c("Estimate","Pr(>|t|)","FDR"), digits=3)
7.3 Vulcanoplot of LMM
glomerulus - tubule
# Categorize Results based on P-value & FDR for plotting
fc_threshold = 0.5
lmm_results$Color <- paste("NS or FC < ",fc_threshold)
lmm_results$Color[lmm_results$`Pr(>|t|)` < 0.05] <- "P < 0.05"
lmm_results$Color[lmm_results$FDR < 0.05] <- "FDR < 0.05"
lmm_results$Color[lmm_results$FDR < 0.001] <- "FDR < 0.001"
lmm_results$Color[abs(lmm_results$Estimate) < fc_threshold] <- "NS or FC < 1"
lmm_results$Color <- factor(lmm_results$Color,
levels = c("NS or FC < 1", "P < 0.05",
"FDR < 0.05", "FDR < 0.001"))
# pick top genes for either side of volcano to label
# order genes for convenience:
lmm_results$invert_P <- (-log10(lmm_results$`Pr(>|t|)`)) * sign(lmm_results$Estimate)
top_g <- c()
#loop here over tested conditions if applicable
#for(location in c("BOTTOM","TOP")) {
top_g <- c(top_g,
lmm_results[, 'Gene'][
order(lmm_results[, 'invert_P'], decreasing = TRUE)[1:30]],
lmm_results[, 'Gene'][
order(lmm_results[, 'invert_P'], decreasing = FALSE)[1:30]])
top_g <- unique(top_g)
lmm_results <- lmm_results[, -1*ncol(lmm_results)] # remove invert_P from matrix
# get significant genes with FDR < 0.001 and fold change > 0.5
#lmm_results_slice <- lmm_results_slice[lmm_results_slice$FDR < 0.001,]
#lmm_results_slice <- lmm_results_slice[lmm_results_slice$Estimate < -0.5 | lmm_results_slice$Estimate > 0.5,]
# Graph results
print(ggplot(lmm_results,
aes(x = Estimate, y = -log10(`Pr(>|t|)`),
color = Color, label = Gene)) +
geom_vline(xintercept = c(fc_threshold, -fc_threshold), lty = "dashed") +
geom_hline(yintercept = -log10(0.05), lty = "dashed") +
geom_point() +
labs(x = paste(lmm_results$Contrast, " log2(FC)"),
y = "Significance, -log10(P)",
color = "Significance") +
scale_color_manual(values = c(`FDR < 0.001` = "dodgerblue",
`FDR < 0.05` = "lightblue",
`P < 0.05` = "orange2",
`NS or FC < 1` = "gray"),
guide = guide_legend(override.aes = list(size = 4))) +
scale_y_continuous(expand = expansion(mult = c(0,0.05))) +
geom_text_repel(data = subset(lmm_results, FDR < 0.001 & abs(lmm_results$Estimate) > fc_threshold),
point.padding = 0.15, color = "black",size=5,
min.segment.length = .1, box.padding = .2, lwd = 2,
max.overlaps = 50) +
theme_bw(base_size = 16) +
theme(legend.position = "bottom"))

#+facet_wrap(~Subset, scales = "free_y"))
DKD - normal
# Categorize Results based on P-value & FDR for plotting
fc_threshold = 0.5
lmm_results_d$Color <- paste("NS or FC < ",fc_threshold)
lmm_results_d$Color[lmm_results_d$`Pr(>|t|)` < 0.05] <- "P < 0.05"
lmm_results_d$Color[lmm_results_d$FDR < 0.05] <- "FDR < 0.05"
lmm_results_d$Color[lmm_results_d$FDR < 0.001] <- "FDR < 0.001"
lmm_results_d$Color[abs(lmm_results_d$Estimate) < fc_threshold] <- "NS or FC < 1"
lmm_results_d$Color <- factor(lmm_results_d$Color,
levels = c("NS or FC < 1", "P < 0.05",
"FDR < 0.05", "FDR < 0.001"))
# pick top genes for either side of volcano to label
# order genes for convenience:
lmm_results_d$invert_P <- (-log10(lmm_results_d$`Pr(>|t|)`)) * sign(lmm_results_d$Estimate)
top_g <- c()
#loop here over tested conditions if applicable
#for(location in c("BOTTOM","TOP")) {
top_g <- c(top_g,
lmm_results_d[, 'Gene'][
order(lmm_results_d[, 'invert_P'], decreasing = TRUE)[1:30]],
lmm_results_d[, 'Gene'][
order(lmm_results_d[, 'invert_P'], decreasing = FALSE)[1:30]])
top_g <- unique(top_g)
lmm_results_d <- lmm_results_d[, -1*ncol(lmm_results_d)] # remove invert_P from matrix
# get significant genes with FDR < 0.001 and fold change > 0.5
#lmm_results_slice <- lmm_results_slice[lmm_results_slice$FDR < 0.001,]
#lmm_results_slice <- lmm_results_slice[lmm_results_slice$Estimate < -0.5 | lmm_results_slice$Estimate > 0.5,]
# Graph results
print(ggplot(lmm_results_d,
aes(x = Estimate, y = -log10(`Pr(>|t|)`),
color = Color, label = Gene)) +
geom_vline(xintercept = c(fc_threshold, -fc_threshold), lty = "dashed") +
geom_hline(yintercept = -log10(0.05), lty = "dashed") +
geom_point() +
labs(x = paste(lmm_results_d$Contrast, " log2(FC)"),
y = "Significance, -log10(P)",
color = "Significance") +
scale_color_manual(values = c(`FDR < 0.001` = "dodgerblue",
`FDR < 0.05` = "lightblue",
`P < 0.05` = "orange2",
`NS or FC < 1` = "gray"),
guide = guide_legend(override.aes = list(size = 4))) +
scale_y_continuous(expand = expansion(mult = c(0,0.05))) +
geom_text_repel(data = subset(lmm_results_d, FDR < 0.001 & abs(lmm_results_d$Estimate) > fc_threshold),
point.padding = 0.15, color = "black",size=5,
min.segment.length = .1, box.padding = .2, lwd = 2,
max.overlaps = 50) +
theme_bw(base_size = 16) +
theme(legend.position = "bottom"))

#+facet_wrap(~Subset, scales = "free_y"))
7.4 Plotting Genes of Interest
my_gois <-unique(subset(lmm_results, `FDR` < 0.001)$Gene)
tmp_tbl<-subset(lmm_results, Gene %in% my_gois)
if(nrow(tmp_tbl) > 1) {
datatable(tmp_tbl,rownames = FALSE,caption = "DE results for Genes of Interest ",filter='top') %>% formatRound(columns=c("Estimate","Pr(>|t|)","FDR"), digits=3)
for (my_goi in my_gois) {
# show expression for a single target
a<-ggplot(pData(target_Data),
aes(x = ANN1, fill = ANN1,
y = assayDataElement(target_Data[my_goi, ], elt = "q_norm"))) +
geom_violin() +
geom_jitter(width = .2) +
labs(y = paste(my_goi,"Expression")) +
scale_y_continuous(trans = "log2") +
facet_wrap(~ANN3, nrow=1) + theme_bw(base_size = 16) +
theme_bw()
a
}
}else{
print("No significant lMM results to plot")
}
7.5 Heatmap of Significant Genes
In addition to generating individual gene box plots or volcano plots,
we can again create a heatmap from our data. This time rather than
utilizing CV to select genes, we can use the P-value or FDR values to
select genes. Here, we plot all genes with an FDR < 0.001.
my_gois <-unique(subset(lmm_results, `FDR` < 0.001)$Gene)
if(length(my_gois)==0) {
print("No significant results to show")
}else{
pheatmap(log2(assayDataElement(target_Data[my_gois, ], elt = "q_norm")),
scale = "row",
show_rownames = TRUE, show_colnames = TRUE,
border_color = NA,
clustering_method = "average",
clustering_distance_rows = "correlation",
clustering_distance_cols = "correlation",
cutree_cols = 3, cutree_rows = 2,
breaks = seq(-3, 3, 0.05),
color = colorRampPalette(c("purple3", "black", "yellow2"))(120),
annotation_colors = color_list,
annotation_col = pData(target_Data)[, ann_names])
}

8 Pathway Analysis
Pathway analysis enables exploration of different aggregate gene sets
for our experimental questions. Each individual ROI/AOI segment is
scored for every pathway of interest, which we can then use to
investigate biological differences. We will perform analogous analyses
as those outlined in the Differential Expression and Spatial
Deconvolution sections of the report for gene set enrichment.
8.1 Scoring Gene Sets
Pathways and gene sets were defined from the Kegg Brite database. We
use an R software package called Gene Set Variation Analysis to score
each segment within our study. see https://cran.r-project.org/web/packages/msigdbr/vignettes/msigdbr-intro.html
for options on collections. We use the KEGG and REACTOME.
h_gene_sets = rbind(msigdbr(species = "human", subcategory = "CP:KEGG"),
msigdbr(species = "human", subcategory = "CP:REACTOME"))
#msigdbr(species = "human", subcategory = "CP:BIOCARTA")
msigdbr_list = split(x = h_gene_sets$gene_symbol, f = h_gene_sets$gs_name)
# prepare df for accurate merging back genes later
pw_gene_df<-data.frame(Pathway = names(msigdbr_list))
pw_gene_df$genes<-msigdbr_list
ssgsea_results <- GSVA::gsva(expr = assayDataElement(target_Data,
elt = "log_q"),
gset.idx.list = msigdbr_list,
method = "zscore",
min.sz = 5,
max.sz = 500,
verbose = FALSE)
geneSetObj <-
NanoStringGeoMxSet(assayData = ssgsea_results,
phenoData = AnnotatedDataFrame(pData(target_Data)),
protocolData = protocolData(target_Data),
featureType = "GeneSet",
check = FALSE)
8.2 Differental analysis of pathways
glomerulus - tubule
# # convert test variables to factors
pData(geneSetObj)[["slide"]]<-factor(pData(geneSetObj)[["slide_name"]])
pData(target_Data)$testRegion<-factor(pData(target_Data)$ANN3, unique(count_mat$ANN3))
lmm_ssgsea_results <- c()
mixedOutmc <-
mixedModelDE(geneSetObj,
elt = "exprs",
modelFormula = ~ testRegion + (1 + testRegion | slide),
groupVar = "testRegion",
nCores = parallel::detectCores(),
multiCore = FALSE)
# format results as data.frame
r_ssgsea_test <- do.call(rbind, mixedOutmc["lsmeans", ])
ssgsea_tests <- rownames(r_ssgsea_test)
r_ssgsea_test <- as.data.frame(r_ssgsea_test)
r_ssgsea_test$Contrast <- ssgsea_tests
#r_ssgsea_test$Genes <- msigdbr_list seems unreliable as gsva omits pathways if genes are not in data..
# use lapply in case you have multiple levels of your test factor to
# correctly associate gene name with it's row in the results table
r_ssgsea_test$Pathway <-
unlist(lapply(colnames(mixedOutmc),
rep, nrow(mixedOutmc["lsmeans", ][[1]])))
r_ssgsea_test$FDR <- p.adjust(r_ssgsea_test$`Pr(>|t|)`, method = "fdr")
r_ssgsea_test <- r_ssgsea_test[, c("Pathway", "Contrast", "Estimate",
"Pr(>|t|)", "FDR")]
lmm_ssgsea_results <- rbind(lmm_ssgsea_results, r_ssgsea_test)
lmm_ssgsea_results <- merge(lmm_ssgsea_results, pw_gene_df)
DKD - normal
# # convert test variables to factors
pData(geneSetObj)[["slide"]]<-factor(pData(geneSetObj)[["slide_name"]])
pData(target_Data)$testClass<-factor(pData(target_Data)$ANN1, unique(count_mat$ANN1))
lmm_ssgsea_results_d <- c()
mixedOutmc <-
mixedModelDE(geneSetObj,
elt = "exprs",
modelFormula = ~ testClass + (1 | slide),
groupVar = "testClass",
nCores = parallel::detectCores(),
multiCore = FALSE)
# format results as data.frame
r_ssgsea_test <- do.call(rbind, mixedOutmc["lsmeans", ])
ssgsea_tests <- rownames(r_ssgsea_test)
r_ssgsea_test <- as.data.frame(r_ssgsea_test)
r_ssgsea_test$Contrast <- ssgsea_tests
#r_ssgsea_test$Genes <- msigdbr_list seems unreliable as gsva omits pathways if genes are not in data..
# use lapply in case you have multiple levels of your test factor to
# correctly associate gene name with it's row in the results table
r_ssgsea_test$Pathway <-
unlist(lapply(colnames(mixedOutmc),
rep, nrow(mixedOutmc["lsmeans", ][[1]])))
r_ssgsea_test$FDR <- p.adjust(r_ssgsea_test$`Pr(>|t|)`, method = "fdr")
r_ssgsea_test <- r_ssgsea_test[, c("Pathway", "Contrast", "Estimate",
"Pr(>|t|)", "FDR")]
lmm_ssgsea_results_d <- rbind(lmm_ssgsea_results_d, r_ssgsea_test)
lmm_ssgsea_results_d <- merge(lmm_ssgsea_results_d, pw_gene_df)
8.2.1 Table of Differental analysis of pathways
glomerulus - tubule
datatable(subset(lmm_ssgsea_results), rownames = FALSE,
extensions = 'Buttons', options = list (
pageLength = 10,
dom = 'Bftrip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
),
caption = "DE results for Pathways",filter='top') %>% formatRound(columns=c("Estimate","Pr(>|t|)","FDR"), digits=5)
DKD - normal
datatable(subset(lmm_ssgsea_results_d), rownames = FALSE,
extensions = 'Buttons', options = list (
pageLength = 10,
dom = 'Bftrip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print')
),
caption = "DE results for Pathways",filter='top') %>% formatRound(columns=c("Estimate","Pr(>|t|)","FDR"), digits=5)
8.3 Vulcanoplot of LMM_Pathways
# Categorize Results based on P-value & FDR for plotting
fc_threshold = 0.5
lmm_ssgsea_results$Color <- "NS or FC < 0.3"
lmm_ssgsea_results$Color[lmm_ssgsea_results$`Pr(>|t|)` < 0.05] <- "P < 0.05"
lmm_ssgsea_results$Color[lmm_ssgsea_results$FDR < 0.05] <- "FDR < 0.05"
lmm_ssgsea_results$Color[lmm_ssgsea_results$FDR < 0.001] <- "FDR < 0.001"
lmm_ssgsea_results$Color[abs(lmm_ssgsea_results$Estimate) < fc_threshold] <- "NS or FC < 0.3"
lmm_ssgsea_results$Color <- factor(lmm_ssgsea_results$Color,
levels = c("NS or FC < 0.3", "P < 0.05",
"FDR < 0.05", "FDR < 0.001"))
# pick top pw for either side of volcano to label
# order pw for convenience:
lmm_ssgsea_results$invert_P <- (-log10(lmm_ssgsea_results$`Pr(>|t|)`)) * sign(lmm_ssgsea_results$Estimate)
top_ssgsea_g <- c()
#loop here over tested conditions if applicable
top_ssgsea_g <- c(top_ssgsea_g,
lmm_ssgsea_results[, 'Pathway'][
order(lmm_ssgsea_results[, 'invert_P'], decreasing = TRUE)[1:20]],
lmm_ssgsea_results[, 'Pathway'][
order(lmm_ssgsea_results[, 'invert_P'], decreasing = FALSE)[1:20]])
top_ssgsea_g <- unique(top_ssgsea_g)
lmm_ssgsea_results <- lmm_ssgsea_results[, -1*ncol(lmm_ssgsea_results)] # remove invert_P from matrix
#lmm_ssgsea_results_slice <- lmm_ssgsea_results_slice[lmm_ssgsea_results_slice$FDR < 1,]
# Graph results
print(ggplot(lmm_ssgsea_results,
aes(x = Estimate, y = -log10(`Pr(>|t|)`),
color = Color, label = Pathway)) +
geom_vline(xintercept = c(0.5, -0.5), lty = "dashed") +
geom_hline(yintercept = -log10(0.05), lty = "dashed") +
geom_point() +
labs(x = paste(lmm_results$Contrast, " FC"),
y = "Significance, -log10(P)",
color = "Significance") +
scale_color_manual(values = c(`FDR < 0.001` = "dodgerblue",
`FDR < 0.05` = "lightblue",
`P < 0.05` = "orange2",
`NS or FC < 0.5` = "gray"),
guide = guide_legend(override.aes = list(size = 4))) +
scale_y_continuous(expand = expansion(mult = c(0,0.05))) +
geom_text_repel(data = subset(lmm_ssgsea_results, Color == "FDR < 0.05" | Color == "FDR < 0.001"),
point.padding = 0.15, color = "black",size=5,
min.segment.length = .1, box.padding = .2, lwd = 2,
max.overlaps = 50) +
theme_bw(base_size = 16) +
theme(legend.position = "bottom"))

# +facet_wrap(~Subset, scales = "free_y"))
8.4 heatmap of pathways
active_pw<-filter(lmm_ssgsea_results, `Pr(>|t|)` < 0.001)$Pathway
active_pw<-filter(lmm_ssgsea_results, FDR < 0.001 )$Pathway
#active_pw<-filter(lmm_ssgsea_results, Color == "FDR < 0.001")$Pathway
active_pw<-top_ssgsea_g
if (length(active_pw)>1) {
print("go")
pw_matrix<-assayDataElement(geneSetObj, elt = "exprs")
pheatmap(pw_matrix[active_pw,],
scale = "row",
show_rownames = TRUE, show_colnames = TRUE,
fontsize_row = 10,
border_color = NA,
clustering_method = "average",
#clustering_distance_rows = "correlation",
clustering_distance_cols = "euclidean",
cutree_cols = 2, cutree_rows = 2,
breaks = seq(-3, 3, 0.05),
#color = colorRampPalette(c("purple3", "black", "yellow2"))(120),
main = "Heatmap of selected Pathways",
annotation_colors = color_list,
annotation_col = pData(target_Data)[, ann_names])
}else{
print("No significant results to display")
}
## [1] "go"

9 Spatial Deconvolution
9.1 Calculate backgrounds
bg = derive_GeoMx_background(
norm = assayDataElement(target_Data , elt = "q_norm"),
probepool = fData(target_Data)$Module,
negnames = c("NegProbe-CTP01","NegProbe-Kilo","Negative Probe", "NegProbe-WTX" ))
#negnames = "NegProbe-WTX")
9.2 Load cell profile
A “cell profile matrix” is a pre-defined matrix that specifies the
expected expression profiles of each cell type in the experiment. The
SpatialDecon library comes with one such matrix pre-loaded, the
“SafeTME” matrix, designed for estimation of immune and stroma cells in
the tumor microenvironment. (This matrix was designed to avoid genes
commonly expressed by cancer cells; see the SpatialDecon manuscript for
details.). Otherwise, load specific profiles from https://github.com/Nanostring-Biostats/CellProfileLibrary/tree/NewProfileMatrices
#safeTME
data("safeTME")
data("safeTME.matches")
current_cell_profile<-safeTME
#see: https://github.com/Nanostring-Biostats/CellProfileLibrary/tree/NewProfileMatrices
# current_cell_profile <- download_profile_matrix(species = "Human",
# age_group = "Adult",
# matrixname = "Bladder_MCA")
heatmap(sweep(current_cell_profile, 1, apply(current_cell_profile, 1, max), "/"),
labRow = NA, margins = c(10, 5), cexCol = 0.7)

9.3 Run spatial deconvolution
# vector identifying pure tumor segments:
#target_Data$istumor = target_Data$ANN3 == "CORE" & target_Data$ANN1 == "PanCK+"
res = runspatialdecon(object = target_Data,
norm_elt = "q_norm",
raw_elt = "exprs",
#is_pure_tumor = target_Data$istumor,
cell_counts = target_Data$nuclei,
X = current_cell_profile,
cellmerges = safeTME.matches, # safeTME.matches object, used by default
#n_tumor_clusters = 5, # how many distinct tumor profiles to append to safeTME
align_genes = TRUE)
9.3.1 Spatial deconvolution
heatmaps
Abundance
# NOTE: check clustering.. why different?
#set display thresholds
thresh <- signif(quantile(res$beta, 0.97), 2)
# plot stored to keep clustering for later
p1<-pheatmap(pmin(t(res$beta),thresh),
#scale = "row",
cutree_cols = 3,
cutree_rows = 2,
fontsize_row = 12,
show_rownames = TRUE, show_colnames = TRUE,
angle_col = "90",
border_color = NA,
#clustering_method = "average",
#clustering_distance_rows = "correlation",
#clustering_distance_cols = "correlation",
legend_breaks = c(round(seq(0, thresh, length.out = 5))[-5], thresh),
legend_labels = c(round(seq(0, thresh, length.out = 5))[-5], paste0("Abundance scores,\ntruncated above at ", thresh)),
#breaks = seq(0, 5, 1),
color = colorRampPalette(c("white","darkblue"))(100),
annotation_colors = color_list,
annotation_col = pData(target_Data)[, ann_names]
)

#p1
Proportional
# proportions:
props <- replace(res$prop_of_nontumor, is.na(res$prop_of_nontumor), 0)
p2<-pheatmap(t(props),
#scale = "row",
cutree_cols = 3,
cutree_rows = 2,
fontsize_row = 12,
show_rownames = TRUE, show_colnames = TRUE,
angle_col = "90",
border_color = NA,
#clustering_method = "average",
#clustering_distance_rows = "correlation",
#clustering_distance_cols = "correlation",
legend_breaks = round(seq(0, max(props) * 0.99, length.out = 5), 2),
legend_labels = c(round(seq(0, max(props), length.out = 5), 2)[-5], "Proportion of all\nfitted populations"),
color = colorRampPalette(c("white","darkblue"))(100),
annotation_colors = color_list,
annotation_col = pData(target_Data)[, ann_names])

#p2
Scaled
# scaled abundances:
epsilon <- min(res$beta[res$beta > 0])
mat <- sweep(res$beta, 1, pmax(apply(res$beta, 1, max), epsilon), "/")
pheatmap(t(mat),
#scale = "row",
cutree_cols = 3,
cutree_rows = 3,
fontsize_row = 12,
show_rownames = TRUE, show_colnames = TRUE,
angle_col = "90",
border_color = NA,
#clustering_method = "average",
#clustering_distance_rows = "correlation",
#clustering_distance_cols = "correlation",
legend_breaks = c(round(seq(0, 1, length.out = 5), 2)[-5], 1),
legend_labels = c(round(seq(0, 1, length.out = 5), 2)[-5], "Scaled abundance\n(ratio to max)"),
color = colorRampPalette(c("white","darkblue"))(100),
annotation_colors = color_list,
annotation_col = pData(target_Data)[, ann_names])

9.4 Barplots
abundance
# define variables to show in heatmaps:
variables_to_plot <- c("slide_name", "region", "class")
col <- cellcols
layout(matrix(c(1, 2, 3, 3), nrow = 2),
widths = c(10, 3, 10, 3),
heights = c(1, 8, 10),
)
par(mar = c(0, 8.2, 0, 0.2))
plot(p1$tree_col, labels = F, main = "", ylab = "", yaxt = "n")
par(mar = c(15, 8, 0, 0))
# data to plot:
mat <- t(res$beta)[, p1$tree_col$order]
# infer scale of negative y-axis for annotation colorbars
ymin <- -max(colSums(mat)) * 0.15
if (!is.finite(ymin)) {
ymin <- 0
}
# draw barplot:
bp <- barplot(mat,
cex.lab = 1.5,
col = col, border = NA,
cex.names = 1.1,
las = 2, main = "", ylab = "Abundance scores",
ylim = c(ymin, max(colSums(mat)))
)
# add color bars for annotations
for (name in rev(variables_to_plot)) {
yrange <- seq(ymin / 3, ymin, length.out = length(variables_to_plot) + 1)[match(name, variables_to_plot) + c(0, 1)]
xwidth <- (bp[2] - bp[1]) / 2
for (i in 1:ncol(mat)) {
rect(bp[i] - xwidth, yrange[2], bp[i] + xwidth, yrange[1],
# border = NA, col = ann_colors[[name]][segmentAnnotations[match(colnames(mat)[i], segmentAnnotations$segmentID), name]]
#border = NA, col = ann_colors[[name]][ann[p1$tree_col$order[i], name]]
border = NA, col = color_list[[name]][ann[colnames(mat)[i], name]]
)
}
}
axis(2,
at = seq(ymin / 3, ymin, length.out = length(variables_to_plot) + 2)[-c(1, length(variables_to_plot) + 2)],
las = 2, labels = variables_to_plot, lty = 0, cex.axis = 1.2
)
#draw a legend:
par(mar = c(0.1, 0.1, 0.1, 0.1))
frame()
legendcols <- legendnames <- c()
#for (name in rev(names(ann_colors))) {
for (name in c("slide_name", "region","class")) {
legendcols <- c(legendcols, NA, color_list[[name]], NA)
legendnames <- c(legendnames, name, names(color_list[[name]]), NA)
}
legend("center",
pch = 15,
cex = 1.5,
col = c(legendcols, rep(NA, 1), rev(col)),
legend = c(legendnames, "Cell type", rev(names(col))),
)

proportional
# define variables to show in heatmaps:
variables_to_plot <- c("slide name", "segment","pheno")
layout(matrix(c(1, 2, 3, 3), nrow = 2),
widths = c(10, 3, 10, 3),
heights = c(1, 8, 10),
)
par(mar = c(0, 8.2, 0, 0.2))
plot(p2$tree_col, labels = F, main = "", ylab = "", yaxt = "n")
par(mar = c(15, 8, 0, 0))
# data to plot:
mat <- t(res$prop_of_nontumor)[, p2$tree_col$order]
mat <- replace(mat, is.na(mat), 0)
# infer scale of negative y-axis for annotation colorbars
ymin <- -0.15
# draw barplot:
bp <- barplot(mat,
cex.lab = 1.5,
col = col, border = NA,
cex.names = 1.1,
las = 2, main = "", ylab = "Proportion of fitted cells",
ylim = c(ymin, max(colSums(mat)))
)
# add color bars for annotations
for (name in rev(variables_to_plot)) {
yrange <- seq(ymin / 3, ymin, length.out = length(variables_to_plot) + 1)[match(name, variables_to_plot) + c(0, 1)]
xwidth <- (bp[2] - bp[1]) / 2
for (i in 1:ncol(mat)) {
rect(bp[i] - xwidth, yrange[2], bp[i] + xwidth, yrange[1],
# border = NA, col = ann_colors[[name]][segmentAnnotations[match(colnames(mat)[i], segmentAnnotations$segmentID), name]]
#border = NA, col = ann_colors[[name]][ann[p2$tree_col$order[i], name]]
border = NA, col = color_list[[name]][ann[colnames(mat)[i], name]]
)
}
}
axis(2,
at = seq(ymin / 3, ymin, length.out = length(variables_to_plot) + 2)[-c(1, length(variables_to_plot) + 2)],
las = 2, labels = variables_to_plot, lty = 0, cex.axis = 1.2
)
#draw a legend:
par(mar = c(0.1, 0.1, 0.1, 0.1))
frame()
legendcols <- legendnames <- c()
#for (name in rev(names(ann_colors))) {
for (name in c("slide name", "segment","pheno")) {
legendcols <- c(legendcols, NA, color_list[[name]], NA)
legendnames <- c(legendnames, name, names(color_list[[name]]), NA)
}
legend("center",
pch = 15,
cex = 1.4,
col = c(legendcols, rep(NA, 1), rev(col)),
legend = c(legendnames, "Cell type", rev(names(col))),
)

LS0tDQp0aXRsZTogIk5hbm9zdHJpbmcgR2VvTXggYW5hbHlzaXMiDQphdXRob3I6ICJJZXMgTmlqbWFuIg0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBmYWxzZQ0KICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMw0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFKQ0KYGBgDQoNCiFbXShDOi9Vc2Vycy9wa2xvb3N0ZXJtYW4vRG9jdW1lbnRzL2dlbmVyYWxfd29ya2Zsb3cvVVNFUS1sb2dvLXN1YnRpdGxlLmpwZykNCg0KIyBLbGFudDogVXNlcQ0KDQojIFByb2plY3Q6IE9yZ2FuIGF0bGFzDQoNCiMgRGF0YXNldDogS2lkbmV5IGRhdGENCg0KKipkYXRlOiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVIICVNICVhICVkICVCLCAlWScpYCoqDQoNCiFbXShDOi9Vc2Vycy9wa2xvb3N0ZXJtYW4vRG9jdW1lbnRzL2dlbmVyYWxfd29ya2Zsb3cvZGVjb3JhdGlvbi1zdHJva2UtZmxhdC12Mi5wbmcpDQoNCioqbG9hZGluZyBkZXBlbmRlbmNpZXMqKiBQbGVhc2UgbWFrZSBzdXJlIHRoZSBmb2xsb3dpbmcgcGFja2FnZXMgYXJlDQppbnN0YWxsZWQgYW5kIHJlcXVpcmVkIGxpYnJhcmllcyBjYW4gYmUgbG9hZGVkOiBcKg0KaW5zdGFsbC5wYWNrYWdlcygicGtnYnVpbGQiKSAvLyBwa2didWlsZDo6Y2hlY2tfYnVpbGRfdG9vbHMoKSBcKg0KaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKSBcKg0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJOYW5vc3RyaW5nLUJpb3N0YXRzL05hbm9TdHJpbmdOQ1Rvb2xzIikgXCoNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiTmFub3N0cmluZy1CaW9zdGF0cy9HZW9teFRvb2xzIiwgcmVmID0gImRldiIpDQpcKiBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoIk5hbm9zdHJpbmctQmlvc3RhdHMvR2VvTXhXb3JrZmxvd3MiLCByZWYgPQ0KIm1haW4iKSBcKiBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiR2VvTXhXb3JrZmxvd3MiKSBcKg0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJEYXZpc0xhYm9yYXRvcnkvc3RhbmRSIikgXCoNCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJTcGF0aWFsRGVjb24iKSBcKiBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiR1NWQSIpIFwqDQppbnN0YWxsLnBhY2thZ2VzKCJwbG90bHkiKSBcKiBpbnN0YWxsLnBhY2thZ2VzKCJEVCIpIFwqDQppbnN0YWxsLnBhY2thZ2VzKCJtc2lnZGJyIikgI2luc3RhbGwucGFja2FnZXMoImRpZ2VzdCIpDQojaW5zdGFsbC5wYWNrYWdlcygicm1hcmtkb3duIikgI2luc3RhbGwucGFja2FnZXMoImthYmxlIikNCg0KYGBge3IgbG9hZF9saWJyYXJpZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCiNsb2FkIGxpYnJhcmllcw0KbGlicmFyeShOYW5vU3RyaW5nTkNUb29scykNCmxpYnJhcnkoR2VvbXhUb29scykNCmxpYnJhcnkoR2VvTXhXb3JrZmxvd3MpDQpsaWJyYXJ5KFNwYXRpYWxEZWNvbikNCmxpYnJhcnkoR1NWQSkgI2ZvciBwYXRod2F5IGFuYWx5c2VzDQpsaWJyYXJ5KG1zaWdkYnIpICNmb3IgbW9sZWN1bGFyIHNpZ25hdHVyZXMgaW4gcGF0aHdheSBhbmFseXNlcw0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdnZm9yY2UpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHNjYWxlcykgIyBmb3IgcGVyY2VudA0KbGlicmFyeShyZXNoYXBlMikgICMgZm9yIG1lbHQNCmxpYnJhcnkoY293cGxvdCkgICAjIGZvciBwbG90X2dyaWQNCmxpYnJhcnkodW1hcCkNCmxpYnJhcnkoUnRzbmUpDQpsaWJyYXJ5KHBoZWF0bWFwKSAgIyBmb3IgcGhlYXRtYXANCmxpYnJhcnkoZ2dyZXBlbCkgDQpsaWJyYXJ5KHNjYWxlcykgI2ZvciBnZ3Bsb3QgcGVhdWRvbG9nIHRvIHByZXZlbnQgZXJyb3JzIG9uIGxvZygwKQ0KbGlicmFyeShEVCkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShncmlkRXh0cmEpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCg0KbGlicmFyeShsaW1tYSkNCmxpYnJhcnkoY2x1c3RlclByb2ZpbGVyKSAjcGF0aHdheSBhbmFseXNpcw0KbGlicmFyeShnYWdlKQ0KYGBgDQoNCiMgMSBsb2FkaW5nIGJhc2UgZmlsZXMNCg0KYGBge3IgbG9hZGluZ19iYXNlX2RhdGF9DQojIFJlZmVyZW5jZSB0aGUgbWFpbiBmb2xkZXIgJ2ZpbGUucGF0aCcgY29udGFpbmluZyB0aGUgc3ViLWZvbGRlcnMgd2l0aCBlYWNoIGRhdGEgZmlsZSB0eXBlOg0KDQpkYXRhZGlyPC1maWxlLnBhdGgoIkM6L1VzZXJzL3BrbG9vc3Rlcm1hbi9Eb2N1bWVudHMvZ2VuZXJhbF93b3JrZmxvdy9LaWRuZXlfRGF0YXNldCIpDQojZGF0YWRpcjwtZmlsZS5wYXRoKCJDOi9Vc2Vycy9waW1rbC9PbmVEcml2ZS9Eb2N1bWVudGVuL1VNQ1UvUi9nZW5lcmFsX3dvcmtmbG93L0tpZG5leV9EYXRhc2V0LyIpDQpgYGANCg0KVG8gbG9jYXRlIGEgc3BlY2lmaWMgZmlsZSBwYXRoIHJlcGxhY2UgdGhlIGFib3ZlIGxpbmUgd2l0aCBkYXRhZGlyIFw8LQ0KZmlsZS5wYXRoKCJcfi9Gb2xkZXIvU3ViRm9sZGVyL0RhdGFMb2NhdGlvbiIpIHJlcGxhY2UgdGhlIEZvbGRlciwNClN1YkZvbGRlciwgRGF0YUxvY2F0aW9uIGFzIG5lZWRlZC4gVGhlIERhdGFMb2NhdGlvbiBmb2xkZXIgc2hvdWxkDQpjb250YWluIGEgZGNjcywgcGtjcywgYW5kIGFubm90YXRpb24gZm9sZGVyIHdpdGggZWFjaCBzZXQgb2YgZmlsZXMNCnByZXNlbnQgYXMgbmVlZGVkIGF1dG9tYXRpY2FsbHkgbGlzdCBmaWxlcyBpbiBlYWNoIGRpcmVjdG9yeSBmb3IgdXNlLg0KDQoqKlRha2UgY2FyZSB5b3UgaW1wb3J0IGEgY29sdW1uIHdpdGggbnVjbGVpIGNvdW50IHNlcGFyYXRlbHkgaWYgeW91DQp3YW50LioqDQoNCmBgYHtyIHBhcnNlX2ZpbGVzfQ0KRENDRmlsZXMgPC0gZGlyKGZpbGUucGF0aChkYXRhZGlyLCAiZGNjcyIpLCBwYXR0ZXJuID0gIi5kY2MkIiwNCiAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVFJVRSwgcmVjdXJzaXZlID0gVFJVRSkNClBLQ0ZpbGVzIDwtIGRpcihmaWxlLnBhdGgoZGF0YWRpciwgInBrY3MiKSwgcGF0dGVybiA9ICIucGtjJCIsDQogICAgICAgICAgICAgICAgZnVsbC5uYW1lcyA9IFRSVUUsIHJlY3Vyc2l2ZSA9IFRSVUUpDQpTYW1wbGVBbm5vdGF0aW9uRmlsZSA8LQ0KICBkaXIoZmlsZS5wYXRoKGRhdGFkaXIsICJhbm5vdGF0aW9uIiksIHBhdHRlcm4gPSAiXltefl0iLA0KICAgICAgZnVsbC5uYW1lcyA9IFRSVUUsIHJlY3Vyc2l2ZSA9IFRSVUUpDQpgYGANCg0KIyAyIGxvYWQgZGF0YQ0KDQpgYGB7ciBsb2FkX2RhdGF9DQpEYXRhIDwtDQogIHJlYWROYW5vU3RyaW5nR2VvTXhTZXQoZGNjRmlsZXMgPSBEQ0NGaWxlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICBwa2NGaWxlcyA9IFBLQ0ZpbGVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHBoZW5vRGF0YUZpbGUgPSBTYW1wbGVBbm5vdGF0aW9uRmlsZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBwaGVub0RhdGFTaGVldCA9ICJUZW1wbGF0ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgcGhlbm9EYXRhRGNjQ29sTmFtZSA9ICJTYW1wbGVfSUQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHByb3RvY29sRGF0YUNvbE5hbWVzID0gYygiYW9pIiwgInJvaSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGV4cGVyaW1lbnREYXRhQ29sTmFtZXMgPSBjKCJwYW5lbCIpKQ0KDQojc2F2ZSBkYXRhIHRvIHByZXZlbnQgbG9hZGluZyB0aW1lIGZvciByZXRha2VzDQpzYXZlRGF0YTwtRGF0YQ0KI0RhdGE8LXNhdmVEYXRhDQoNCiNjaGFuZ2UgRGF0YSBjb2x1bW4gbmFtZXMgYW5kIG1hbnVhbCBjb3JyZWN0aW9uIG9mIHNwZWxsaW5nIGVycm9ycw0KRGF0YUBwaGVub0RhdGFAZGF0YVtbInNsaWRlX25hbWUiXV08LURhdGFAcGhlbm9EYXRhQGRhdGFbWyJzbGlkZSBuYW1lIl1dDQpEYXRhQHBoZW5vRGF0YUBkYXRhW1sic2xpZGUgbmFtZSJdXTwtICBOVUxMDQpEYXRhQHBoZW5vRGF0YUBkYXRhW1siQU5ONCJdXTwtZ3N1YigiaSIsICIiLCBEYXRhQHBoZW5vRGF0YUBkYXRhW1siQU5ONCJdXSkNCg0KIysxIHJlZmVyZW5jZXMgdGhlIHNsaWRlIG5hbWUgY29sdW1uDQphbm5fc2l6ZTwtbGVuZ3RoKGNvbG5hbWVzKERhdGFAcGhlbm9EYXRhQGRhdGEpW2dyZXBsKCJBTk4iLGNvbG5hbWVzKERhdGFAcGhlbm9EYXRhQGRhdGEpKV0pKzEgDQphbm5fbmFtZXM8LWMoY29sbmFtZXMoRGF0YUBwaGVub0RhdGFAZGF0YSlbZ3JlcGwoIkFOTiIsY29sbmFtZXMoRGF0YUBwaGVub0RhdGFAZGF0YSkpXSwic2xpZGVfbmFtZSIpDQoNCiMgRmVlbCBmcmVlIHRvIGNoYW5nZSB0aGUgb3JkZXIgb2Ygd2hpY2ggY29sb3JzIGFyZSBhcHBvaW50ZWQuDQpjb2xvcjwtYygiI0EzNDlBNCIsICIjRkZGRjMzIiwgIiNFNzI5OEEiLCAiIzA5MTgzMyIsICIjMUI5RTc3IiwgIiNEOTVGMDIiLCAiIzc1NzBCMyIsICAiIzY2QTYxRSIsICIjRTZBQjAyIiwgIiM4REQzQzciLCAiIzlGMDAwRiIsICIjQkVCQURBIiwgIiNGQjgwNzIiLCAiIzgwQjFEMyIsICIjRkRCNDYyIiwgIiNCM0RFNjkiLCAiI0ZDQ0RFNSIsICIjRDlEOUQ5IiwgIiNCQzgwQkQiLCAiI0NDRUJDNSIsICIjRkZFRDZGIiwgIiMzNzdFQjgiLCAiIzk4NEVBMyIsICIjNERBRjRBIiwgIiNGRjcxQ0UiLCAiI0ZGN0YwMCIsICIjQTZDRUUzIiwgIiMxRjc4QjQiLCAiI0IyREY4QSIsICIjMzNBMDJDIiwgIiNGQjlBOTkiLCAiI0UzMUExQyIsICIjRkRCRjZGIiwgIiNDQUIyRDYiLCAiIzZBM0Q5QSIsICIjRkZGRjk5IiwgIiNCMTU5MjgiKQ0Kc2hvd19jb2woY29sb3IpDQoNCiMgVXNlIGNvdW50IGFuZCBjb3VudF9tYXggdG8gc2V0IHRoZSByYW5nZSBvZiBjb2xvciBuZWVkZWQgZm9yIGVhY2ggY29sdW1uLg0KIyBXaXRoIHNldE5hbWVzKCkgdGhlIGNvbG9yIGNvZGUgaXMgbGlua2VkIHRvIGVhY2ggdW5pcXVlIGluZGl2aWR1YWwgdmFsdWUgb2YgZWFjaCBjb2x1bW4uDQpjb3VudD0xDQpjb2xvcl9saXN0ID0gbGlzdCgpDQpmb3IgKGFubiBpbiBhbm5fbmFtZXMpIHsNCiAgY291bnRfbWF4ID0gY291bnQrbGVuZ3RoKHVuaXF1ZShEYXRhQHBoZW5vRGF0YUBkYXRhW1thbm5dXSkpLTENCiAgY29sb3JfbGlzdFtbYW5uXV08LXNldE5hbWVzKGNvbG9yW2NvdW50OmNvdW50X21heF0sIHVuaXF1ZShEYXRhQHBoZW5vRGF0YUBkYXRhW1thbm5dXSkpDQogIGNvdW50PWNvdW50X21heCsxDQp9DQpjb2xvcl9saXN0DQpgYGANCg0KYGBge3J9DQpwYXN0ZSgiUmVhZHMgZnJvbSBmb2xsb3dpbmcgcnVucyB1c2VkOiAiLHVuaXF1ZShwRGF0YShwcm90b2NvbERhdGEoRGF0YSkpJFNlcVNldElkKSkNCg0KYGBgDQoNCiMgMyBTdHVkeSBkZXNpZ24NCg0KYGBge3IgYW5ub3RhdGV9DQpwa2NzIDwtIGFubm90YXRpb24oRGF0YSkNCm1vZHVsZXMgPC0gZ3N1YigiLnBrYyIsICIiLCBwa2NzKQ0Ka2FibGUoZGF0YS5mcmFtZShQS0NzID0gcGtjcywgbW9kdWxlcyA9IG1vZHVsZXMpKQ0KYGBgDQoNClNlbGVjdCB0aGUgYW5ub3RhdGlvbnMgd2Ugd2FudCB0byBzaG93LCB1c2UgXGBcYCB0byBzdXJyb3VuZCBjb2x1bW4NCm5hbWVzIHdpdGggc3BhY2VzIG9yIHNwZWNpYWwgc3ltYm9scw0KDQpgYGB7ciBzZWxlY3RfYW5ub3RhdGlvbnN9DQpjb3VudF9tYXQgPC0gZHBseXI6OmNvdW50KHBEYXRhKERhdGEpLCBBTk4xLEFOTjIsQU5OMyxBTk40LHNsaWRlX25hbWUpDQpgYGANCg0KU2ltcGxpZnkgdGhlIHNsaWRlIG5hbWVzIGlmIHJlcXVpcmVkDQoNCmBgYHtyIHNpbXBsaWZ5X25hbWVzfQ0KY291bnRfbWF0JHNsaWRlX25hbWUgPC0gZ3N1YigiZGlzZWFzZSIsICJkIiwgZ3N1Yigibm9ybWFsIiwgIm4iLCBjb3VudF9tYXQkc2xpZGVfbmFtZSkpDQpjb3VudF9tYXQkQU5OMiA8LSBnc3ViKCJkaXNlYXNlIiwgImQiLCBnc3ViKCJub3JtYWwiLCAibiIsIGNvdW50X21hdCRBTk4yKSkNCiNjb3VudF9tYXQkcGF0aF9hbm4gPC0gZ3N1YigiaSIsICIiLCBjb3VudF9tYXQkcGF0aF9hbm4pICNjb3JyZWN0aW5nIHNwZWxsaW5nIGVycm9yDQpgYGANCg0KR2F0aGVyIHRoZSBkYXRhIGFuZCBwbG90IGluIG9yZGVyOiBjbGFzcywgc2xpZGUgbmFtZSwgcmVnaW9uLCBzZWdtZW50DQoNCmBgYHtyIGdhdGhlcl9kYXRhfQ0KdGVzdF9nciA8LSBnYXRoZXJfc2V0X2RhdGEoY291bnRfbWF0LCAxOmFubl9zaXplKQ0KdGVzdF9nciR4IDwtIGZhY3Rvcih0ZXN0X2dyJHgsIGxldmVscyA9IGFubl9uYW1lcykNCmBgYA0KDQpQbG90IFNhbmtleQ0KDQpgYGB7ciBTYW5rZXlfcGxvdCwgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MTF9DQpnZ3Bsb3QodGVzdF9nciwgaGVpZ2h0ID0gMTAsIHdpZHRoID0gMTAsIGFlcyh4LCBpZCA9IGlkLCBzcGxpdCA9IHksIHZhbHVlID0gbikpICsNCiAgZ2VvbV9wYXJhbGxlbF9zZXRzKGFlcyhmaWxsID0gQU5OMyksIGFscGhhID0gMC41LCBheGlzLndpZHRoID0gMC4xKSArDQogIGdlb21fcGFyYWxsZWxfc2V0c19heGVzKGF4aXMud2lkdGggPSAwLjIpICsNCiAgZ2VvbV9wYXJhbGxlbF9zZXRzX2xhYmVscyhjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSA1KSArDQogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTIpICsgDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbigwKSkgKyANCiAgc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQgPSBleHBhbnNpb24oMCkpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiIikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29sb3JfbGlzdCRBTk4zKSArDQogIGFubm90YXRlKGdlb20gPSAic2VnbWVudCIsIHggPSA0LjI1LCB4ZW5kID0gNC4yNSwNCiAgICAgICAgICAgeSA9IDEwLCB5ZW5kID0gNjEsIGx3ZCA9IDIpICsNCiAgYW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgeCA9IDQuMTksIHkgPSAyNSwgYW5nbGUgPSA5MCwgc2l6ZSA9IDUsDQogICAgICAgICAgIGhqdXN0ID0gMC41LCBsYWJlbCA9ICI1MCBzZWdtZW50cyIpDQpgYGANCg0KIyA0IFFDICYgUHJlLXByb2Nlc3NpbmcNCg0KU2hpZnQgY291bnRzIHRvIG9uZQ0KDQpgYGB7ciBzaGlmdF9jb3VudHN9DQojc2hpZnQgYW55IGV4cHJlc3Npb24gY291bnRzIHdpdGggYSB2YWx1ZSBvZiAwIHRvIDEgdG8gZW5hYmxlIGluIGRvd25zdHJlYW0gdHJhbnNmb3JtYXRpb25zLg0KRGF0YSA8LSBzaGlmdENvdW50c09uZShEYXRhLCB1c2VEQUxvZ2ljID0gVFJVRSkNCmBgYA0KDQojIDQuMSBTZWdtZW50IFFDDQoNCldlIGZpcnN0IGFzc2VzcyBzZXF1ZW5jaW5nIHF1YWxpdHkgYW5kIGFkZXF1YXRlIHRpc3N1ZSBzYW1wbGluZyBmb3INCmV2ZXJ5IFJPSS9BT0kgc2VnbWVudC4NCg0KRXZlcnkgUk9JL0FPSSBzZWdtZW50IHdpbGwgYmUgdGVzdGVkIGZvcjoNCg0KUmF3IHNlcXVlbmNpbmcgcmVhZHM6IHNlZ21lbnRzIHdpdGggXD4xMDAwIHJhdyByZWFkcyBhcmUgcmVtb3ZlZC4gJQ0KQWxpZ25lZCwlIFRyaW1tZWQsIG9yICUgU3RpdGNoZWQgc2VxdWVuY2luZyByZWFkczogc2VnbWVudHMgYmVsb3cgXH44MCUNCmZvciBvbmUgb3IgbW9yZSBvZiB0aGVzZSBRQyBwYXJhbWV0ZXJzIGFyZSByZW1vdmVkLiAlIFNlcXVlbmNpbmcNCnNhdHVyYXRpb24gKFsxLWRlZHVwbGljYXRlZCByZWFkcy9hbGlnbmVkIHJlYWRzXSUpOiBzZWdtZW50cyBiZWxvdyBcfjUwJQ0KcmVxdWlyZSBhZGRpdGlvbmFsIHNlcXVlbmNpbmcgdG8gY2FwdHVyZSBmdWxsIHNhbXBsZSBkaXZlcnNpdHkgYW5kIGFyZQ0Kbm90IHR5cGljYWxseSBhbmFseXplZCB1bnRpbCBpbXByb3ZlZC4gTmVnYXRpdmUgQ291bnQ6IHRoaXMgaXMgdGhlDQpnZW9tZXRyaWMgbWVhbiBvZiB0aGUgc2V2ZXJhbCB1bmlxdWUgbmVnYXRpdmUgcHJvYmVzIGluIHRoZSBHZW9NeCBwYW5lbA0KdGhhdCBkbyBub3QgdGFyZ2V0IG1STkEgYW5kIGVzdGFibGlzaCB0aGUgYmFja2dyb3VuZCBjb3VudCBsZXZlbCBwZXINCnNlZ21lbnQ7IHNlZ21lbnRzIHdpdGggbG93IG5lZ2F0aXZlIGNvdW50cyAoMS0xMCkgYXJlIG5vdCBuZWNlc3NhcmlseQ0KcmVtb3ZlZCBidXQgbWF5IGJlIHN0dWRpZWQgY2xvc2VyIGZvciBsb3cgZW5kb2dlbm91cyBnZW5lIHNpZ25hbCBhbmQvb3INCmluc3VmZmljaWVudCB0aXNzdWUgc2FtcGxpbmcuIE5vIFRlbXBsYXRlIENvbnRyb2wgKE5UQykgY291bnQ6IHZhbHVlcw0KXD4xLDAwMCBjb3VsZCBpbmRpY2F0ZSBjb250YW1pbmF0aW9uIGZvciB0aGUgc2VnbWVudHMgYXNzb2NpYXRlZCB3aXRoDQp0aGlzIE5UQzsgaG93ZXZlciwgaW4gY2FzZXMgd2hlcmUgdGhlIE5UQyBjb3VudCBpcyBiZXR3ZWVuIDEsMDAwLQ0KMTAsMDAwLCB0aGUgc2VnbWVudHMgbWF5IGJlIHVzZWQgaWYgdGhlIE5UQyBkYXRhIGlzIHVuaWZvcm1seSBsb3cgKGUuZy4NCjAtMiBjb3VudHMgZm9yIGFsbCBwcm9iZXMpLiBOdWNsZWk6IFw+MTAwIG51Y2xlaSBwZXIgc2VnbWVudCBpcw0KZ2VuZXJhbGx5IHJlY29tbWVuZGVkOyBob3dldmVyLCB0aGlzIGN1dG9mZiBpcyBoaWdobHkgc3R1ZHkvdGlzc3VlDQpkZXBlbmRlbnQgYW5kIG1heSBuZWVkIHRvIGJlIHJlZHVjZWQ7IHdoYXQgaXMgbW9zdCBpbXBvcnRhbnQgaXMNCmNvbnNpc3RlbmN5IGluIHRoZSBudWNsZWkgZGlzdHJpYnV0aW9uIGZvciBzZWdtZW50cyB3aXRoaW4gdGhlIHN0dWR5Lg0KQXJlYTogZ2VuZXJhbGx5IGNvcnJlbGF0ZXMgd2l0aCBudWNsZWk7IGEgc3RyaWN0IGN1dG9mZiBpcyBub3QgZ2VuZXJhbGx5DQphcHBsaWVkIGJhc2VkIG9uIGFyZWEuDQoNCiMgNC4xLjEgU2VsZWN0IFNlZ21lbnQgUUMNCg0KRmlyc3QsIHdlIHNlbGVjdCB0aGUgUUMgcGFyYW1ldGVyIGN1dG9mZnMsIGFnYWluc3Qgd2hpY2ggb3VyIFJPSS9BT0kNCnNlZ21lbnRzIHdpbGwgYmUgdGVzdGVkIGFuZCBmbGFnZ2VkIGFwcHJvcHJpYXRlbHkuIFdlIGhhdmUgc2VsZWN0ZWQgdGhlDQphcHByb3ByaWF0ZSBzdHVkeS1zcGVjaWZpYyBwYXJhbWV0ZXJzIGZvciB0aGlzIHN0dWR5LiBOb3RlOiB0aGUgZGVmYXVsdA0KUUMgdmFsdWVzIHJlY29tbWVuZGVkIGFib3ZlIGFyZSBhZHZpc2VkIHdoZW4gc3VydmV5aW5nIGEgbmV3IGRhdGFzZXQgZm9yDQp0aGUgZmlyc3QgdGltZS4NCg0KRGVmYXVsdCBRQyBjdXRvZmZzIGFyZSBjb21tZW50ZWQgaW4gKCkgYWRqYWNlbnQgdG8gdGhlIHJlc3BlY3RpdmUNCnBhcmFtZXRlcnMgc3R1ZHktc3BlY2lmaWMgdmFsdWVzIHdlcmUgc2VsZWN0ZWQgYWZ0ZXIgdmlzdWFsaXppbmcgdGhlIFFDDQpyZXN1bHRzIGluIG1vcmUgZGV0YWlsIGJlbG93DQoNCmBgYHtyIHNldF9RQ19wYXJhbXN9DQpRQ19wYXJhbXMgPC0NCiAgbGlzdChtaW5TZWdtZW50UmVhZHMgPSAxMDAwLCAjIE1pbmltdW0gbnVtYmVyIG9mIHJlYWRzICgxMDAwKQ0KICAgICAgIHBlcmNlbnRUcmltbWVkID0gODAsICAgICMgTWluaW11bSAlIG9mIHJlYWRzIHRyaW1tZWQgKDgwJSkNCiAgICAgICBwZXJjZW50U3RpdGNoZWQgPSA4MCwgICAjIE1pbmltdW0gJSBvZiByZWFkcyBzdGl0Y2hlZCAoODAlKQ0KICAgICAgIHBlcmNlbnRBbGlnbmVkID0gNzUsICAgICMgTWluaW11bSAlIG9mIHJlYWRzIGFsaWduZWQgKDgwJSkNCiAgICAgICBwZXJjZW50U2F0dXJhdGlvbiA9IDUwLCAjIE1pbmltdW0gc2VxdWVuY2luZyBzYXR1cmF0aW9uICg1MCUpDQogICAgICAgbWluTmVnYXRpdmVDb3VudCA9IDEsICAgIyBNaW5pbXVtIG5lZ2F0aXZlIGNvbnRyb2wgY291bnRzICgxMCkNCiAgICAgICBtYXhOVENDb3VudCA9IDkwMDAsICAgICAjIE1heGltdW0gY291bnRzIG9ic2VydmVkIGluIE5UQyB3ZWxsICgxMDAwKQ0KICAgICAgIG1pbk51Y2xlaSA9IDIwLCAgICAgICAgIyBNaW5pbXVtICMgb2YgbnVjbGVpIGVzdGltYXRlZCAoMTAwKQ0KICAgICAgIG1pbkFyZWEgPSAxMDAwKSAgICAgICAgICMgTWluaW11bSBzZWdtZW50IGFyZWEgKDUwMDApDQpEYXRhIDwtDQogIHNldFNlZ21lbnRRQ0ZsYWdzKERhdGEsIHFjQ3V0b2ZmcyA9IFFDX3BhcmFtcykgICAgICAgIA0KDQpjYXQoInByZS1RQyBmZWF0dXJlczoiLCBkaW0oRGF0YSlbMV0sICJcbnByZS1RQyBzYW1wbGVzOiIsIGRpbShEYXRhKVsyXSkNCg0KI1RhYmxlIGZvciBjbGFyaWZpY2F0aW9uDQpRQ3BhcmFtc19kZiA8LSBkYXRhLmZyYW1lICgNCiAgaXRlbXMgPSBjKCJtaW5TZWdtZW50UmVhZHMiLCJwZXJjZW50VHJpbW1lZCIsInBlcmNlbnRTdGl0Y2hlZCIsInBlcmNlbnRBbGlnbmVkIiwicGVyY2VudFNhdHVyYXRpb24iLA0KICAgICAgICAgICAgIm1pbk5lZ2F0aXZlQ291bnQiLCJtYXhOVENDb3VudCIsIm1pbk51Y2xlaSIsIm1pbkFyZWEiKSwNCiAgZGVmYXVsdHMgPSBjKDEwMDAsODAsODAsODAsNTAsMTAsMTAwMCwxMDAsNTAwMCksDQogIGFjdHVhbCA9IGMoUUNfcGFyYW1zJG1pblNlZ21lbnRSZWFkcyxRQ19wYXJhbXMkcGVyY2VudFRyaW1tZWQsUUNfcGFyYW1zJHBlcmNlbnRTdGl0Y2hlZCxRQ19wYXJhbXMkcGVyY2VudEFsaWduZWQsUUNfcGFyYW1zJHBlcmNlbnRTYXR1cmF0aW9uLFFDX3BhcmFtcyRtaW5OZWdhdGl2ZUNvdW50LFFDX3BhcmFtcyRtYXhOVENDb3VudCxRQ19wYXJhbXMkbWluTnVjbGVpLFFDX3BhcmFtcyRtaW5BcmVhKQ0KKQ0KDQpkYXRhdGFibGUoUUNwYXJhbXNfZGYsIHJvd25hbWVzPUZBTFNFLA0KICAgICAgICAgIGNhcHRpb24gPSAiUUMgdGhyZXNob2xkcyIsDQogICAgICAgICAgZXh0ZW5zaW9ucyA9ICdCdXR0b25zJywgb3B0aW9ucyA9IGxpc3QgKA0KICAgICAgICAgICAgZG9tID0gJ0JmdHJpcCcsDQogICAgICAgICAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JykNCiAgICAgICAgICApDQopDQoNCmBgYA0KDQpDb2xsYXRlIFFDIFJlc3VsdHMNCg0KYGBge3IgY29sbGF0ZV9RQ19yZXN1bHRzfQ0KUUNSZXN1bHRzIDwtIHByb3RvY29sRGF0YShEYXRhKVtbIlFDRmxhZ3MiXV0NCmZsYWdfY29sdW1ucyA8LSBjb2xuYW1lcyhRQ1Jlc3VsdHMpDQpRQ19TdW1tYXJ5IDwtIGRhdGEuZnJhbWUoUGFzcyA9IGNvbFN1bXMoIVFDUmVzdWx0c1ssIGZsYWdfY29sdW1uc10pLA0KICAgICAgICAgICAgICAgICAgICAgICAgIFdhcm5pbmcgPSBjb2xTdW1zKFFDUmVzdWx0c1ssIGZsYWdfY29sdW1uc10pKQ0KDQpRQ1Jlc3VsdHMkUUNTdGF0dXMgPC0gYXBwbHkoUUNSZXN1bHRzLCAxTCwgZnVuY3Rpb24oeCkgew0KICBpZmVsc2Uoc3VtKHgpID09IDBMLCAiUEFTUyIsICJXQVJOSU5HIikNCn0pDQoNClFDX1N1bW1hcnlbIlRPVEFMIEZMQUdTIiwgXSA8LQ0KICBjKHN1bShRQ1Jlc3VsdHNbLCAiUUNTdGF0dXMiXSA9PSAiUEFTUyIpLA0KICAgIHN1bShRQ1Jlc3VsdHNbLCAiUUNTdGF0dXMiXSA9PSAiV0FSTklORyIpKQ0KDQpjb2xfYnkgPC0gIkFOTjEiDQpjb2xfYnlfcGxhdGUgPC0gIlBsYXRlX0lEIg0KYGBgDQoNCiMgNC4yIEdyYXBoaWNhbCBzdW1tYXJpZXMgb2YgUUMgc3RhdGlzdGljcyB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQpVc2UgdGhlIHRhYi1tZW51IHRvIG5hdmlnYXRlIQ0KDQpgYGB7ciBRQ19wbG90dGluZ30NClFDX2hpc3RvZ3JhbSA8LSBmdW5jdGlvbihhc3NheV9kYXRhID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsX2J5ID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICB0aHIgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX3RyYW5zID0gTlVMTCkgew0KICBwbHQgPC0gZ2dwbG90KGFzc2F5X2RhdGEsDQogICAgICAgICAgICAgICAgYWVzX3N0cmluZyh4ID0gcGFzdGUwKCJ1bmxpc3QoYCIsIGFubm90YXRpb24sICJgKSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGZpbGxfYnkpKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDUwKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gdGhyLCBsdHkgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmxhY2siKSArDQogICAgdGhlbWVfYncoKSArIGd1aWRlcyhmaWxsID0gIm5vbmUiKSArDQogICAgZmFjZXRfd3JhcChhcy5mb3JtdWxhKHBhc3RlKCJ+IiwgZmlsbF9ieSkpLCBucm93ID0gNCkgKw0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jb2xvcl9saXN0JEFOTjEpICsNCiAgICBsYWJzKHggPSBhbm5vdGF0aW9uLCB5ID0gInNlZ21lbnRzLCAjIiwgdGl0bGUgPSBhbm5vdGF0aW9uKQ0KICBpZighaXMubnVsbChzY2FsZV90cmFucykpIHsNCiAgICBwbHQgPC0gcGx0ICsNCiAgICAgIHNjYWxlX3hfY29udGludW91cyh0cmFucyA9IHNjYWxlX3RyYW5zKQ0KICB9DQogIHBsdA0KfQ0KYGBgDQoNCiMjIFRyaW1tZWQNCg0KYGBge3J9DQpRQ19oaXN0b2dyYW0oc0RhdGEoRGF0YSksICJUcmltbWVkICglKSIsIGNvbF9ieSwgUUNfcGFyYW1zJHBlcmNlbnRUcmltbWVkKQ0KYGBgDQoNCiMjIFN0aWNoZWQgKCUpDQoNCmBgYHtyfQ0KUUNfaGlzdG9ncmFtKHNEYXRhKERhdGEpLCAiU3RpdGNoZWQgKCUpIiwgY29sX2J5LCBRQ19wYXJhbXMkcGVyY2VudFN0aXRjaGVkKQ0KYGBgDQoNCiMjIEFsaWduZWQgKCUpDQoNCmBgYHtyfQ0KUUNfaGlzdG9ncmFtKHNEYXRhKERhdGEpLCAiQWxpZ25lZCAoJSkiLCBjb2xfYnksUUNfcGFyYW1zJHBlcmNlbnRBbGlnbmVkKQ0KYGBgDQoNCiMjIFNlcXVlbmNpbmcgU2F0dXJhdGlvbiAoJSkgey5hY3RpdmV9DQoNCmBgYHtyfQ0KUUNfaGlzdG9ncmFtKHNEYXRhKERhdGEpLCAiU2F0dXJhdGVkICglKSIsIGNvbF9ieSwgUUNfcGFyYW1zJHBlcmNlbnRTYXR1cmF0aW9uKSArDQogIGxhYnModGl0bGUgPSAiU2VxdWVuY2luZyBTYXR1cmF0aW9uICglKSIsDQogICAgICAgeCA9ICJTZXF1ZW5jaW5nIFNhdHVyYXRpb24gKCUpIikNCmBgYA0KDQojIyBBcmVhDQoNCmBgYHtyfQ0KUUNfaGlzdG9ncmFtKHNEYXRhKERhdGEpLCAiYXJlYSIsIGNvbF9ieSwgUUNfcGFyYW1zJG1pbkFyZWEsIHNjYWxlX3RyYW5zID0gImxvZzEwIikNCmBgYA0KDQojIyBOdWNsZWkgY291bnQNCg0KYGBge3J9DQpRQ19oaXN0b2dyYW0oc0RhdGEoRGF0YSksICJudWNsZWkiLCBjb2xfYnksIFFDX3BhcmFtcyRtaW5OdWNsZWkpDQpgYGANCg0KIyMgRHVwbGljYXRpb25SYXRlDQoNCmBgYHtyfQ0KZ2dwbG90KHBEYXRhKHByb3RvY29sRGF0YShEYXRhKSksDQogICAgICAgYWVzKHggPSBQbGF0ZV9JRCwgZmlsbD1QbGF0ZV9JRCwNCiAgICAgICAgICB5ID0gKERlZHVwbGljYXRlZFJlYWRzL1JhdykpKSArDQogIGdlb21fdmlvbGluKCkgKw0KICBnZW9tX2ppdHRlcih3aWR0aCA9IC4yKSArDQogIGxhYnMoeSA9ICJEZWR1cGxpY2F0ZWQgLyBSYXcgcmVhZHMiKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsNCiAgdGhlbWVfYncoKQ0KDQojIFBsYXRlX0lEIHRvIGNoZWNrIHRoZSBsb3cgZHViL3JhdyBjb3VudA0KUUNfaGlzdG9ncmFtKHNEYXRhKERhdGEpLCAiU2F0dXJhdGVkICglKSIsIGNvbF9ieV9wbGF0ZSwgUUNfcGFyYW1zJHBlcmNlbnRTYXR1cmF0aW9uKSArDQogIGxhYnModGl0bGUgPSAiU2VxdWVuY2luZyBTYXR1cmF0aW9uICglKSIsDQogICAgICAgeCA9ICJTZXF1ZW5jaW5nIFNhdHVyYXRpb24gKCUpIikNCg0KYGBgDQoNCiMjIE5lZ3Byb2JlcyB2cyBFbmRvZ2Vub3VzDQoNCmBgYHtyIHBsb3RfbmVncHJvYmVfZGF0YSwgZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9NX0NCnRtcF90YXJnZXRfRGF0YSA8LSBhZ2dyZWdhdGVDb3VudHMoRGF0YSkNCg0KI2dldCBuZWdhdGl2ZSBwcm9iZSBkYXRhDQpuZWdzPC1zdWJzZXQodG1wX3RhcmdldF9EYXRhLENvZGVDbGFzcz09Ik5lZ2F0aXZlIikNCg0KcDE8LWdncGxvdChwRGF0YShuZWdzKSwNCiAgICAgICBhZXMoeCA9IEFOTjEsIGZpbGwgPSBBTk4xLA0KICAgICAgICAgIHkgPSBhc3NheURhdGFFbGVtZW50KG5lZ3MsIGVsdCA9ICJleHBycyIpKSkgKw0KICBnZW9tX3Zpb2xpbigpICsNCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAuMikgKw0KICBsYWJzKHkgPSAiTmVnYXRpdmUgcHJvYmVzIEV4cHJlc3Npb24iKSArDQogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDEsMzAwMCksIHRyYW5zID0gImxvZzIiKSArDQogIHRoZW1lX2J3KCkNCg0KDQojIGdldCBlbmRvZ2Vub3VzIHByb2JlIGRhdGENCmVuZDwtc3Vic2V0KHRtcF90YXJnZXRfRGF0YSxDb2RlQ2xhc3M9PSJFbmRvZ2Vub3VzIikNCg0KcDI8LWdncGxvdChwRGF0YShlbmQpLA0KICAgICAgIGFlcyh4ID0gQU5OMSwgZmlsbCA9IEFOTjEsDQogICAgICAgICAgIHkgPSBjb2xNZWFucyhhc3NheURhdGFFbGVtZW50KGVuZCwgZWx0ID0gImV4cHJzIikpKSkgKw0KICBnZW9tX3Zpb2xpbigpICsNCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAuMikgKw0KICBsYWJzKHkgPSAiRW5kb2dlbm91cyBwcm9iZXMgRXhwcmVzc2lvbiAobWVhbikiKSArDQogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDEsMzAwMCksdHJhbnMgPSAibG9nMiIpICsNCiAgdGhlbWVfYncoKQ0KDQpwbCA8LWxpc3QocDEscDIpDQpwbG90X2dyaWQocGxvdGxpc3Q9cGwsIG5yb3c9MSwgYWxpZ249J2gnKQ0KDQpgYGANCg0KIyMgTmVnX3Byb2JlIHJlYWRzIGNvbXBhcmVkIHRvIHJhd19yZWFkcw0KDQpgYGB7cn0NCg0KIyBtYWtlIGJhY2tncm91bmQgdG90YWwgbmVnIHByb2JlIGNvdW50DQpmZGF0YV9kZjwtZkRhdGEoRGF0YSkNCm5lZ3Byb2Jlc25hbWVzPC1yb3duYW1lcyhmZGF0YV9kZltmZGF0YV9kZiROZWdhdGl2ZT09VFJVRSxdKQ0KdGVtcF9leHA8LWFzc2F5RGF0YUVsZW1lbnQoRGF0YSxlbHQ9J2V4cHJzJykNCm5lZ3Byb2JlX2V4cHJfZmQ8LXRlbXBfZXhwW3Jvd25hbWVzKHRlbXBfZXhwKSAlaW4lIG5lZ3Byb2Jlc25hbWVzLF0NCnRvdF9uZWdfY3RybF9yZWFkczwtY29sU3VtcyhuZWdwcm9iZV9leHByX2ZkKQ0KdG90X2RlZHVwX3JlYWRzPC1wRGF0YShwcm90b2NvbERhdGEoRGF0YSkpJERlZHVwbGljYXRlZFJlYWRzDQoNCmRmPC1kYXRhLmZyYW1lKCdhb2knPSBuYW1lcyh0b3RfbmVnX2N0cmxfcmVhZHMpLCd0b3RfZGVkdXBfcmVhZHMnID0gYXMubnVtZXJpYyh0b3RfZGVkdXBfcmVhZHMpLCd0b3RfbmVnX2N0cmxfcmVhZHMnPWFzLm51bWVyaWModG90X25lZ19jdHJsX3JlYWRzKSkNCmRmPC1tZWx0KGRmLGlkPSJhb2kiKQ0KZ2dwbG90KGRmLGFlcyhmaWxsPXZhcmlhYmxlLHk9dmFsdWUseD1hb2kpKSArIA0KICBnZW9tX2Jhcihwb3NpdGlvbj0iaWRlbnRpdHkiLHN0YXQ9ImlkZW50aXR5IikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSBsb2cyX3RyYW5zKCkpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCkpICAgICAgICAgICAgICAgICAgICAgDQogDQpgYGANCg0KIyMgRHVwbGljYXRlZCByZWFkcyB2cyBCYWNrZ3JvdW5kDQoNCmBgYHtyfQ0KIyBnZXQgZGNjIHBlciBwbGF0ZS4gc3VtIG5lZ3Byb2JlIGNvdW50cy9kY2MvcGxhdGUNCmdncGxvdChwRGF0YShwcm90b2NvbERhdGEoRGF0YSkpLA0KICAgICAgIGFlcyh4ID0gUGxhdGVfSUQsIGZpbGw9UGxhdGVfSUQsDQogICAgICAgICAgeSA9IERlZHVwbGljYXRlZFJlYWRzKSkgKw0KICBnZW9tX3Zpb2xpbigpICsNCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAuMikgKw0KICBsYWJzKHkgPSAiRGVkdXBsaWNhdGVkIC8gUmF3IHJlYWRzIikgKw0KICBzY2FsZV95X2xvZzEwKCkrDQogIGdlb21faGxpbmUoZGF0YSA9cERhdGEocHJvdG9jb2xEYXRhKERhdGEpKSAsIA0KICAgICAgICAgICBhZXMoeWludGVyY2VwdCA9IE5UQywgY29sb3VyPVBsYXRlX0lEKSkgKw0KICB0aGVtZV9idygpDQoNCmBgYA0KDQojIyBEdXBsaWNhdGVkIHJlYWRzIHZzIFJPSWFyZWENCg0KYGBge3IsIGZpZy53aWR0aD0xNSxmaWcuaGVpZ2h0PTV9DQp0ZW1wX2RmPC1jYmluZChwRGF0YShEYXRhKSxwRGF0YShwcm90b2NvbERhdGEoRGF0YSkpLGRjYz1yb3duYW1lcyhwRGF0YShEYXRhKSkpDQoNCmdncGxvdCh0ZW1wX2RmLA0KICAgICAgIGFlcyh4ID0gZGNjLCBjb2xvdXI9c2xpZGVfbmFtZSwNCiAgICAgICAgICB5ID0gKERlZHVwbGljYXRlZFJlYWRzL2FyZWEpICkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgeWxpbSgwLDIwKSArIA0KICBsYWJzKHkgPSAiRGVkdXBsaWNhdGVkIHJlYWRzIC8gUk9JIGFyZWEiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPTYsIGFuZ2xlPTkwLCBoanVzdD0xKSApDQoNCmBgYA0KDQojIyBEdXBsaWNhdGVkIHJlYWRzIHZzIG51Y2xlaQ0KDQpgYGB7ciwgZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9NX0NCnRlbXBfZGY8LWNiaW5kKHBEYXRhKERhdGEpLHBEYXRhKHByb3RvY29sRGF0YShEYXRhKSksZGNjPXJvd25hbWVzKHBEYXRhKERhdGEpKSkNCg0KZ2dwbG90KHRlbXBfZGYsDQogICAgICAgYWVzKHggPSBkY2MsIGNvbG91cj1zbGlkZV9uYW1lLA0KICAgICAgICAgIHkgPSAoRGVkdXBsaWNhdGVkUmVhZHMvbnVjbGVpKSApKSArDQogIGdlb21fcG9pbnQoKSArDQogIHlsaW0oMCwyMDApICsNCiAgbGFicyh5ID0gIkRlZHVwbGljYXRlZCByZWFkcyAvIG51Y2xlaSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9NiwgYW5nbGU9OTAsIGhqdXN0PTEpICkNCg0KYGBgDQoNCiMgNC4zIFByb2Nlc3MgTmVnYXRpdmUgR2VvTWVhbnMNCg0KYGBge3J9DQojIENhbGN1bGF0ZSB0aGUgbmVnYXRpdmUgZ2VvbWV0cmljIG1lYW5zIGZvciBlYWNoIG1vZHVsZQ0KIyBJdCB3aWxsIHNob3cgb25seSB0aGUgbmVnYXRpdmUgcHJvYmVzIGdlb21lYW4sIHNvIGV4cGVjdCBsZXNzIHNlZ21lbnRzLg0KbmVnYXRpdmVHZW9NZWFucyA8LSANCiAgZXNCeShuZWdhdGl2ZUNvbnRyb2xTdWJzZXQoRGF0YSksIA0KICAgICAgIEdST1VQID0gIk1vZHVsZSIsIA0KICAgICAgIEZVTiA9IGZ1bmN0aW9uKHgpIHsgDQogICAgICAgICBhc3NheURhdGFBcHBseSh4LCBNQVJHSU4gPSAyLCBGVU4gPSBuZ2VvTWVhbiwgZWx0ID0gImV4cHJzIikgDQogICAgICAgfSkgDQpwcm90b2NvbERhdGEoRGF0YSlbWyJOZWdHZW9NZWFuIl1dIDwtIG5lZ2F0aXZlR2VvTWVhbnMNCg0KbmVnQ29scyA8LSBwYXN0ZTAoIk5lZ0dlb01lYW5fIiwgbW9kdWxlcykNCnBEYXRhKERhdGEpWywgbmVnQ29sc10gPC0gc0RhdGEoRGF0YSlbWyJOZWdHZW9NZWFuIl1dDQpmb3IoYW5uIGluIG5lZ0NvbHMpIHsNCiAgcGx0IDwtIFFDX2hpc3RvZ3JhbShwRGF0YShEYXRhKSwgYW5uLCBjb2xfYnksIDIsIHNjYWxlX3RyYW5zID0gImxvZzEwIikNCiAgcHJpbnQocGx0KQ0KfQ0KDQoNCiMgRGV0YXRjaCBuZWdfZ2VvbWVhbiBjb2x1bW5zIGFoZWFkIG9mIGFnZ3JlZ2F0ZUNvdW50cyBjYWxsDQoNCnBEYXRhKERhdGEpIDwtIHBEYXRhKERhdGEpWywgIWNvbG5hbWVzKHBEYXRhKERhdGEpKSAlaW4lIG5lZ0NvbHNdDQoNCmBgYA0KDQpTaG93IGFsbCBOVEMgdmFsdWVzLCBGcmVxID0gXCMgb2YgU2VnbWVudHMgd2l0aCBhIGdpdmVuIE5UQyBjb3VudDoNCg0KYGBge3IgUUNfdGFibGVzfQ0KUUM8LXNEYXRhKERhdGEpDQoNCm50Y19kZiA8LVFDWyxjKCJzbGlkZV9uYW1lIiwiUGxhdGVfSUQiLCJOVENfSUQiLCJOVEMiKV0NCnRlbXB0YWJsZTwtbnRjX2RmICU+JSBkcGx5cjo6Y291bnQobnRjX2RmJHNsaWRlX25hbWUsbnRjX2RmJE5UQ19JRCxudGNfZGYkUGxhdGVfSUQsbnRjX2RmJE5UQykNCmNvbG5hbWVzKHRlbXB0YWJsZSkgPC0gYygiU2xpZGVfbmFtZSIsIk5UQ19JRCIsIlBsYXRlX0lEIiwiTlRDX2NvdW50IiwiTnVtYmVyX29mX3NhbXBsZXMiKQ0KZGF0YXRhYmxlKHRlbXB0YWJsZSwgcm93bmFtZXMgPSBGQUxTRSkNCg0KDQprYWJsZSh0YWJsZShOVENfQ291bnQgPSBzRGF0YShEYXRhKSROVEMpLCBjb2wubmFtZXMgPSBjKCJOVEMgQ291bnQiLCAiIyBvZiBTZWdtZW50cyIpKQ0KDQprYWJsZShRQ19TdW1tYXJ5LCBjYXB0aW9uID0gIlFDIFN1bW1hcnkgVGFibGUgZm9yIGVhY2ggU2VnbWVudCIpDQoNCmRhdGF0YWJsZShRQ19TdW1tYXJ5LA0KICAgICAgICAgIGNhcHRpb24gPSAiQU9JIFFDIFN1bW1hcnkiLA0KICAgICAgICAgIGV4dGVuc2lvbnMgPSAnQnV0dG9ucycsIG9wdGlvbnMgPSBsaXN0ICgNCiAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgYnV0dG9ucyA9IGMoJ2NvcHknLCAnY3N2JywgJ2V4Y2VsJywgJ3BkZicsICdwcmludCcpDQogICAgICAgICAgKQ0KKQ0KYGBgDQoNClNob3cgQU9JcyB3aGljaCBmYWlsIGNyaXRpY2FsIFFDcy4NCg0KYGBge3IgbGlzdF9mYWlsdXJlc30NClFDPC1zRGF0YShEYXRhKQ0KdW5kZXJzYXQ8LXN1YnNldChRQywgYFNhdHVyYXRlZCAoJSlgPD0gUUNfcGFyYW1zJHBlcmNlbnRTYXR1cmF0aW9uKQ0KDQppZihucm93KHVuZGVyc2F0KT4gMCkgew0KDQpkYXRhdGFibGUoYWdncmVnYXRlKHVuZGVyc2F0LCBieT1saXN0KHVuZGVyc2F0JFNhbXBsZUlEKSxwYXN0ZSxjb2xsYXBzZT0iOyIpLA0KICAgICAgICAgIGV4dGVuc2lvbnMgPSAnQnV0dG9ucycsIG9wdGlvbnMgPSBsaXN0ICgNCiAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgYnV0dG9ucyA9IGMoJ2NvcHknLCAnY3N2JywgJ2V4Y2VsJywgJ3BkZicsICdwcmludCcpDQogICAgICAgICAgKQ0KKX0NCmBgYA0KDQpTdWJzZXR0aW5nIG91ciBkYXRhc2V0IGhhcyByZW1vdmVkIHNhbXBsZXMgd2hpY2ggZGlkIG5vdCBwYXNzIFFDDQoNCmBgYHtyIHN1YnNldHRpbmdfUUNfZmFpbHN9DQpEYXRhIDwtIERhdGFbLCBRQ1Jlc3VsdHMkUUNTdGF0dXMgPT0gIlBBU1MiXQ0KYGBgDQoNCkdlbmVyYWxseSBrZWVwIHRoZSBxY0N1dG9mZnMgcGFyYW1ldGVycyB1bmNoYW5nZWQuIFNldA0KcmVtb3ZlTG9jYWxPdXRsaWVycyB0byBGQUxTRSBpZiB5b3UgZG8gbm90IHdhbnQgdG8gcmVtb3ZlIGxvY2FsIG91dGxpZXJzDQoNCmBgYHtyIHByb2Nlc3NfUUN9DQpEYXRhIDwtIHNldEJpb1Byb2JlUUNGbGFncyhEYXRhLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxY0N1dG9mZnMgPSBsaXN0KG1pblByb2JlUmF0aW8gPSAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwZXJjZW50RmFpbEdydWJicyA9IDIwKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlTG9jYWxPdXRsaWVycyA9IEZBTFNFKQ0KDQpQcm9iZVFDUmVzdWx0cyA8LSBmRGF0YShEYXRhKVtbIlFDRmxhZ3MiXV0NCmBgYA0KDQpEZWZpbmUgUUMgdGFibGUgZm9yIFByb2JlIFFDDQoNCmBgYHtyIGRlZmluZV9xY190YWJsZX0NCnFjX2RmIDwtIGRhdGEuZnJhbWUoUGFzc2VkID0gc3VtKHJvd1N1bXMoUHJvYmVRQ1Jlc3VsdHNbLCAtMV0pID09IDApLA0KICAgICAgICAgICAgICAgICAgICBHbG9iYWwgPSBzdW0oUHJvYmVRQ1Jlc3VsdHMkR2xvYmFsR3J1YmJzT3V0bGllciksDQogICAgICAgICAgICAgICAgICAgIExvY2FsID0gc3VtKHJvd1N1bXMoUHJvYmVRQ1Jlc3VsdHNbLCAtMjotMV0pID4gMA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmICFQcm9iZVFDUmVzdWx0cyRHbG9iYWxHcnViYnNPdXRsaWVyKSkNCmBgYA0KDQpTdWJzZXQgb2JqZWN0IHRvIGV4Y2x1ZGUgYWxsIHRoYXQgZGlkIG5vdCBwYXNzIFJhdGlvICYgR2xvYmFsIHRlc3RpbmcNCg0KYGBge3Igc3Vic2V0fQ0KUHJvYmVRQ1Bhc3NlZCA8LSANCiAgc3Vic2V0KERhdGEsIA0KICAgICAgICAgZkRhdGEoRGF0YSlbWyJRQ0ZsYWdzIl1dWyxjKCJMb3dQcm9iZVJhdGlvIildID09IEZBTFNFICYNCiAgICAgICAgICAgZkRhdGEoRGF0YSlbWyJRQ0ZsYWdzIl1dWyxjKCJHbG9iYWxHcnViYnNPdXRsaWVyIildID09IEZBTFNFKQ0KDQpEYXRhIDwtIFByb2JlUUNQYXNzZWQgDQpjYXQoIkFmdGVyIFFDIGZlYXR1cmVzOiIsIGRpbShEYXRhKVsxXSwgIlxuQWZ0ZXIgUUMgc2FtcGxlczoiLCBkaW0oRGF0YSlbMl0pDQpgYGANCg0KQ2hlY2sgaG93IG1hbnkgdW5pcXVlIHRhcmdldHMgdGhlIG9iamVjdCBoYXMNCg0KYGBge3IgdW5pcXVlX2NoZWNrfQ0KbGVuZ3RoKHVuaXF1ZShmZWF0dXJlRGF0YShEYXRhKVtbIlRhcmdldE5hbWUiXV0pKQ0KYGBgDQoNCkNvbGxhcHNlIHRvIHRhcmdldHMNCg0KYGBge3IgY29sbGFwc190YXJnZXRzfQ0KdGFyZ2V0X0RhdGEgPC0gYWdncmVnYXRlQ291bnRzKERhdGEpDQoNCmV4cHJzKHRhcmdldF9EYXRhKVsxOjUsIDE6Ml0NCmBgYA0KDQpEZWZpbmUgTE9RIFNEIHRocmVzaG9sZCBhbmQgbWluaW11bSB2YWx1ZQ0KDQpgYGB7ciBzZXRfTFNRfQ0KY3V0b2ZmIDwtIDINCm1pbkxPUSA8LSAyDQpgYGANCg0KIyA0LjQgTGltaXQgb2YgUXVhbnRpZmljYXRpb24NCg0KV2UgZGVmaW5lIGEgbGltaXQgb2YgcXVhbnRpZmljYXRpb24gKExPUSkgcGVyIFJPSS9BT0kgc2VnbWVudCBiYXNlZCBvbg0KdGhlIG5lZ2F0aXZlIGNvbnRyb2wgcHJvYmVzIHRvIGd1aWRlIHRoZSBmaWx0ZXJpbmcgb2Ygc2VnbWVudHMgYW5kIGdlbmVzDQp3aXRoIGxvdyBzaWduYWwgcmVsYXRpdmUgdG8gYmFja2dyb3VuZC4gVGhlIGZvcm11bGEgZm9yIGNhbGN1bGF0aW5nIHRoZQ0KTE9RIGluIHRoZSAkaV57dGh9JCBzZWdtZW50IGF0ICRuJCBzdGFuZGFyZCBkZXZpYXRpb25zICgkbiA9IDIkIGZvciB0aGlzDQpzdHVkeSkgaXM6ICRMT1FfaT1nZW9tZWFuKE5lZ1Byb2JlX2kpKmdlb1NEKE5lZ1Byb2JlX2kpXm4kDQoNCkNhbGN1bGF0ZSBMT1EgcGVyIG1vZHVsZSB0ZXN0ZWQNCg0KYGBge3IgY2FsY3VsYXRlX0xPUX0NCkxPUSA8LSBkYXRhLmZyYW1lKHJvdy5uYW1lcyA9IGNvbG5hbWVzKHRhcmdldF9EYXRhKSkNCmZvcihtb2R1bGUgaW4gbW9kdWxlcykgew0KICB2YXJzIDwtIHBhc3RlMChjKCJOZWdHZW9NZWFuXyIsICJOZWdHZW9TRF8iKSwNCiAgICAgICAgICAgICAgICAgbW9kdWxlKQ0KICBpZihhbGwodmFyc1sxOjJdICVpbiUgY29sbmFtZXMocERhdGEodGFyZ2V0X0RhdGEpKSkpIHsNCiAgICBMT1FbLCBtb2R1bGVdIDwtDQogICAgICBwbWF4KG1pbkxPUSwNCiAgICAgICAgICAgcERhdGEodGFyZ2V0X0RhdGEpWywgdmFyc1sxXV0gKiANCiAgICAgICAgICAgICBwRGF0YSh0YXJnZXRfRGF0YSlbLCB2YXJzWzJdXSBeIGN1dG9mZikNCiAgfQ0KfQ0KcERhdGEodGFyZ2V0X0RhdGEpJExPUSA8LSBMT1ENCmBgYA0KDQojIDQuNSBGaWx0ZXJpbmcNCg0KQWZ0ZXIgZGV0ZXJtaW5pbmcgdGhlIGxpbWl0IG9mIHF1YW50aWZpY2F0aW9uIChMT1EpIHBlciBzZWdtZW50LCB3ZQ0KcmVjb21tZW5kIGZpbHRlcmluZyBvdXQgZWl0aGVyIHNlZ21lbnRzIGFuZC9vciBnZW5lcyB3aXRoIGFibm9ybWFsbHkgbG93DQpzaWduYWwuIEZpbHRlcmluZyBpcyBhbiBpbXBvcnRhbnQgc3RlcCB0byBmb2N1cyBvbiB0aGUgdHJ1ZSBiaW9sb2dpY2FsDQpkYXRhIG9mIGludGVyZXN0Lg0KDQpXZSBkZXRlcm1pbmUgdGhlIG51bWJlciBvZiBnZW5lcyBkZXRlY3RlZCBpbiBlYWNoIHNlZ21lbnQgYWNyb3NzIHRoZQ0KZGF0YXNldC4NCg0KYGBge3IgZmlsdGVyaW5nfQ0KTE9RX01hdCA8LSBjKCkNCmZvcihtb2R1bGUgaW4gbW9kdWxlcykgew0KICBpbmQgPC0gZkRhdGEodGFyZ2V0X0RhdGEpJE1vZHVsZSA9PSBtb2R1bGUNCiAgTWF0X2kgPC0gdChlc0FwcGx5KHRhcmdldF9EYXRhW2luZCwgXSwgTUFSR0lOID0gMSwNCiAgICAgICAgICAgICAgICAgICAgIEZVTiA9IGZ1bmN0aW9uKHgpIHsNCiAgICAgICAgICAgICAgICAgICAgICAgeCA+IExPUVssIG1vZHVsZV0NCiAgICAgICAgICAgICAgICAgICAgIH0pKQ0KICBMT1FfTWF0IDwtIHJiaW5kKExPUV9NYXQsIE1hdF9pKQ0KfQ0KIyBlbnN1cmUgb3JkZXJpbmcgc2luY2UgdGhpcyBpcyBzdG9yZWQgb3V0c2lkZSBvZiB0aGUgZ2VvbXhTZXQNCkxPUV9NYXQgPC0gTE9RX01hdFtmRGF0YSh0YXJnZXRfRGF0YSkkVGFyZ2V0TmFtZSwgXQ0KYGBgDQoNCiMgNC41LjEgU2VnbWVudCBHZW5lIERldGVjdGlvbg0KDQpXZSBmaXJzdCBmaWx0ZXIgb3V0IHNlZ21lbnRzIHdpdGggZXhjZXB0aW9uYWxseSBsb3cgc2lnbmFsLiBUaGVzZQ0Kc2VnbWVudHMgd2lsbCBoYXZlIGEgc21hbGwgZnJhY3Rpb24gb2YgcGFuZWwgZ2VuZXMgZGV0ZWN0ZWQgYWJvdmUgdGhlDQpMT1EgcmVsYXRpdmUgdG8gdGhlIG90aGVyIHNlZ21lbnRzIGluIHRoZSBzdHVkeS4gTGV0J3MgdmlzdWFsaXplIHRoZQ0KZGlzdHJpYnV0aW9uIG9mIHNlZ21lbnRzIHdpdGggcmVzcGVjdCB0byB0aGVpciAlIGdlbmVzIGRldGVjdGVkOg0KDQpTYXZlIGRldGVjdGlvbiByYXRlIGluZm9ybWF0aW9uIHRvIHBoZW5vIGRhdGENCg0KYGBge3Igc2F2ZV9kZXRlY3Rpbm9fcmF0ZX0NCnBEYXRhKHRhcmdldF9EYXRhKSRHZW5lc0RldGVjdGVkIDwtIA0KICBjb2xTdW1zKExPUV9NYXQsIG5hLnJtID0gVFJVRSkNCnBEYXRhKHRhcmdldF9EYXRhKSRHZW5lRGV0ZWN0aW9uUmF0ZSA8LQ0KICBwRGF0YSh0YXJnZXRfRGF0YSkkR2VuZXNEZXRlY3RlZCAvIG5yb3codGFyZ2V0X0RhdGEpDQoNCmBgYA0KDQpEZXRlcm1pbmUgZGV0ZWN0aW9uIHRocmVzaG9sZHM6IDElLCA1JSwgMTAlLCAxNSUsIFw+MTUlDQoNCmBgYHtyIGRldGVybWluZSt0aHJlc2hvbGRzfQ0KcERhdGEodGFyZ2V0X0RhdGEpJERldGVjdGlvblRocmVzaG9sZCA8LSANCiAgY3V0KHBEYXRhKHRhcmdldF9EYXRhKSRHZW5lRGV0ZWN0aW9uUmF0ZSwNCiAgICAgIGJyZWFrcyA9IGMoMCwgMC4wMSwgMC4wNSwgMC4xLCAwLjE1LCAwLjIsMSksDQogICAgICBsYWJlbHMgPSBjKCI8MSUiLCAiMS01JSIsICI1LTEwJSIsICIxMC0xNSUiLCAiMTUtMjAlIiwgIj4yMCUiKSkNCg0KIyBzdGFja2VkIGJhciBwbG90IG9mIGRpZmZlcmVudCBjdXQgcG9pbnRzICgxJSwgNSUsIDEwJSwgMTUlKQ0KZ2dwbG90KHBEYXRhKHRhcmdldF9EYXRhKSwNCiAgICAgICBhZXMoeCA9IERldGVjdGlvblRocmVzaG9sZCkpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBBTk4xKSkgKw0KICBnZW9tX3RleHQoc3RhdCA9ICJjb3VudCIsIGFlcyhsYWJlbCA9IC4uY291bnQuLiksIHZqdXN0ID0gLTAuNSkgKw0KICB0aGVtZV9idygpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwLjEpKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29sb3JfbGlzdCRBTk4xKSArDQogIGxhYnMoeCA9ICJHZW5lIERldGVjdGlvbiBSYXRlIiwNCiAgICAgICB5ID0gIlNlZ21lbnRzLCAjIiwNCiAgICAgICBmaWxsID0gIlNlZ21lbnQgVHlwZSIpDQpgYGANCg0KY3V0IHBlcmNlbnQgZ2VuZXMgZGV0ZWN0ZWQgYXQgMSwgNSwgMTAsIDE1DQoNCmBgYHtyIGN1dF90b19wZXJjZW50fQ0Ka2FibGUodGFibGUocERhdGEodGFyZ2V0X0RhdGEpJERldGVjdGlvblRocmVzaG9sZCwNCiAgICAgICAgICAgIHBEYXRhKHRhcmdldF9EYXRhKSRBTk4xKSkNCg0KIyBzZXQgdGhyZXNob2xkIGZvciBkZXRlY3Rpb25sZXZlbA0KIyBkZWZhdWx0IDAuMQ0KZ2VuZV9kZXRfdGhyZXNob2xkIDwtIDAuMDUNCg0KdGFyZ2V0X0RhdGEgPC0NCiAgdGFyZ2V0X0RhdGFbLCBwRGF0YSh0YXJnZXRfRGF0YSkkR2VuZURldGVjdGlvblJhdGUgPj0gZ2VuZV9kZXRfdGhyZXNob2xkXQ0KDQpkaW0odGFyZ2V0X0RhdGEpDQpgYGANCg0KIyA0LjUuMiBjb2xsZWN0IGFubm90YXRpb25zDQoNCmBgYHtyIHNlbGVjdF9hbm5vdGF0aW9uczJ9DQoNCiMgKipTZWxlY3QgdGhlIGFubm90YXRpb25zIHdlIHdhbnQgdG8gc2hvdywgdXNlIGBgIHRvIHN1cnJvdW5kIGNvbHVtbiBuYW1lcyB3aXRoIHNwYWNlcyBvciBzcGVjaWFsIHN5bWJvbHMqKg0Kb2xkX2NvdW50X21hdDwtY291bnRfbWF0DQpjb3VudF9tYXQgPC0gZHBseXI6OmNvdW50KHBEYXRhKERhdGEpLCBBTk4xLEFOTjIsQU5OMyxBTk40LHNsaWRlX25hbWUpDQoNCiMgc2ltcGxpZnlfc2xpZGVfbmFtZXMgaWYgbmVlZGVkDQojY291bnRfbWF0JHNsaWRlX25hbWUgPC0gZ3N1YigiZGlzZWFzZSIsICJkIiwgZ3N1Yigibm9ybWFsIiwgIm4iLCBjb3VudF9tYXQkc2xpZGVfbmFtZSkpDQoNCiMgZ2F0aGVyIHRoZSBkYXRhIGFuZCBwbG90IGluIG9yZGVyOiBjbGFzcywgc2xpZGUgbmFtZSwgcmVnaW9uLCBzZWdtZW50DQp0ZXN0X2dyIDwtIGdhdGhlcl9zZXRfZGF0YShjb3VudF9tYXQsIDE6YW5uX3NpemUpDQp0ZXN0X2dyJHggPC0gDQogIGZhY3Rvcih0ZXN0X2dyJHgsIA0KICAgICAgICAgbGV2ZWxzID0gYW5uX25hbWVzKQ0KDQoNCmFvaWxpc3QgPC1uYW1lcyhhcy5kYXRhLmZyYW1lKGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGEsIGVsdD0gImV4cHJzIikpKQ0KDQpBTk4xIDwtYXMuZGF0YS5mcmFtZShwRGF0YSh0YXJnZXRfRGF0YSkkQU5OMSx1bmlxdWUoY291bnRfbWF0JEFOTjEpKQ0KY29sbmFtZXMoQU5OMSkgPC0gImNsYXNzIg0Kcm93Lm5hbWVzKEFOTjEpIDwtYW9pbGlzdA0KDQpBTk4yIDwtYXMuZGF0YS5mcmFtZShwRGF0YSh0YXJnZXRfRGF0YSkkQU5OMix1bmlxdWUoY291bnRfbWF0JEFOTjIpKQ0KY29sbmFtZXMoQU5OMikgPC0gInNsaWRlX2FubiINCnJvdy5uYW1lcyhBTk4yKSA8LWFvaWxpc3QNCg0KQU5OMyA8LWFzLmRhdGEuZnJhbWUocERhdGEodGFyZ2V0X0RhdGEpJEFOTjMsdW5pcXVlKGNvdW50X21hdCRBTk4zKSkNCmNvbG5hbWVzKEFOTjMpIDwtICJyZWdpb24iDQpyb3cubmFtZXMoQU5OMykgPC1hb2lsaXN0DQoNCkFOTjQgPC1hcy5kYXRhLmZyYW1lKHBEYXRhKHRhcmdldF9EYXRhKSRBTk40LHVuaXF1ZShjb3VudF9tYXQkQU5ONCkpDQpjb2xuYW1lcyhBTk40KSA8LSAicGF0aCINCnJvdy5uYW1lcyhBTk40KSA8LWFvaWxpc3QNCg0KU04gPC1hcy5kYXRhLmZyYW1lKHBEYXRhKHRhcmdldF9EYXRhKSRzbGlkZV9uYW1lLCB1bmlxdWUoY291bnRfbWF0JHNsaWRlX25hbWUpKQ0KY29sbmFtZXMoU04pIDwtICJzbGlkZV9uYW1lIg0Kcm93Lm5hbWVzKFNOKSA8LWFvaWxpc3QNCg0KYW5uPC1jYmluZChBTk4xLEFOTjIsQU5OMyxBTk40LFNOKQ0KYGBgDQoNCiMgNC42IE1hbnVhbCByZW1vdmFsIG9mIHNhbXBsZXMvY2xhc3Nlcw0KDQpgYGB7ciByZW1vdmVfc2FtcGxlc30NCmFjdGl2ZV9hb2lzPC1yb3duYW1lcyhhbm4pDQpgYGANCg0KcmUtQ29sbGVjdCBhbm5vdGF0aW9ucw0KDQpgYGB7ciBjb2xsZWN0X2Fubm90YXRpb25zfQ0KIyBnYXRoZXIgdGhlIGRhdGEgYW5kIHBsb3QgaW4gb3JkZXI6IGNsYXNzLCBzbGlkZSBuYW1lLCByZWdpb24sIHNlZ21lbnQNCnRlc3RfZ3IgPC0gZ2F0aGVyX3NldF9kYXRhKGNvdW50X21hdCwgMTphbm5fc2l6ZSkNCnRlc3RfZ3IkeCA8LQ0KICBmYWN0b3IodGVzdF9nciR4LA0KICAgICAgICAgbGV2ZWxzID0gYW5uX25hbWVzKQ0KDQpgYGANCg0KcmUtUGxvdCBTYW5rZXkNCg0KYGBge3IgcGxvdF9zYW5rZXksIGZpZy53aWR0aD0yMCxmaWcuaGVpZ2h0PTExfQ0KZ2dwbG90KHRlc3RfZ3IsIGFlcyh4LCBpZCA9IGlkLCBzcGxpdCA9IHksIHZhbHVlID0gbikpICsNCiAgZ2VvbV9wYXJhbGxlbF9zZXRzKGFlcyhmaWxsID0gQU5OMSksIGFscGhhID0gMC41LCBheGlzLndpZHRoID0gMC4xKSArDQogIGdlb21fcGFyYWxsZWxfc2V0c19heGVzKGF4aXMud2lkdGggPSAwLjIpICsNCiAgZ2VvbV9wYXJhbGxlbF9zZXRzX2xhYmVscyhjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSA1KSArDQogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTcpICsgDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbigwKSkgKyANCiAgc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQgPSBleHBhbnNpb24oMCkpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNvbG9yX2xpc3QkQU5OMSkgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiKSArDQogIGFubm90YXRlKGdlb20gPSAic2VnbWVudCIsIHggPSAzLjI1LCB4ZW5kID0gMy4yNSwgeSA9IDEwLCANCiAgICAgICAgICAgeWVuZCA9IDYwLCBsd2QgPSAyKSArDQogIGFubm90YXRlKGdlb20gPSAidGV4dCIsIHggPSAzLjE5LCB5ID0gMjUsIGFuZ2xlID0gOTAsIHNpemUgPSA1LA0KICAgICAgICAgICBoanVzdCA9IDAuNSwgbGFiZWwgPSAiNTAgc2VnbWVudHMiKQ0KYGBgDQoNCiMgNC43IEdlbmUgRGV0ZWN0aW9uIFJhdGUNCg0KQ2FsY3VsYXRlIGRldGVjdGlvbiByYXRlDQoNCmBgYHtyIGNsY19kZXRlY3Rpb25fcmF0ZX0NCkxPUV9NYXQgPC0gTE9RX01hdFssIGNvbG5hbWVzKHRhcmdldF9EYXRhKV0NCmZEYXRhKHRhcmdldF9EYXRhKSREZXRlY3RlZFNlZ21lbnRzIDwtIHJvd1N1bXMoTE9RX01hdCwgbmEucm0gPSBUUlVFKQ0KZkRhdGEodGFyZ2V0X0RhdGEpJERldGVjdGlvblJhdGUgPC0NCiAgZkRhdGEodGFyZ2V0X0RhdGEpJERldGVjdGVkU2VnbWVudHMgLyBucm93KHBEYXRhKHRhcmdldF9EYXRhKSkNCmBgYA0KDQpHZW5lIG9mIGludGVyZXN0IGRldGVjdGlvbiB0YWJsZQ0KDQpgYGB7ciBnZW5lX29mX2ludGVyZXN0X3RhYmxlfQ0KZ29pIDwtIGMoIlBEQ0QxIiwgIkNEMjc0IiwgIklGTkciLCAiQ0Q4QSIsICJDRDY4IiwgIkVQQ0FNIiwNCiAgICAgICAgICJLUlQxOCIsICJOUEhTMSIsICJOUEhTMiIsICJDQUxCMSIsICJDTEROOCIpDQpnb2lfZGYgPC0gZGF0YS5mcmFtZSgNCiAgR2VuZSA9IGdvaSwNCiAgTnVtYmVyID0gZkRhdGEodGFyZ2V0X0RhdGEpW2dvaSwgIkRldGVjdGVkU2VnbWVudHMiXSwNCiAgRGV0ZWN0aW9uUmF0ZSA9IHBlcmNlbnQoZkRhdGEodGFyZ2V0X0RhdGEpW2dvaSwgIkRldGVjdGlvblJhdGUiXSkpDQpgYGANCg0KIyA0LjggR2VuZSBGaWx0ZXJpbmcNCg0KV2Ugd2lsbCBncmFwaCB0aGUgdG90YWwgbnVtYmVyIG9mIGdlbmVzIGRldGVjdGVkIGluIGRpZmZlcmVudA0KcGVyY2VudGFnZXMgb2Ygc2VnbWVudHMuIEJhc2VkIG9uIHRoZSB2aXN1YWxpemF0aW9uIGJlbG93LCB3ZSBjYW4gYmV0dGVyDQp1bmRlcnN0YW5kIGdsb2JhbCBnZW5lIGRldGVjdGlvbiBpbiBvdXIgc3R1ZHkgYW5kIHNlbGVjdCBob3cgbWFueSBsb3cNCmRldGVjdGVkIGdlbmVzIHRvIGZpbHRlciBvdXQgb2YgdGhlIGRhdGFzZXQuIEdlbmUgZmlsdGVyaW5nIGluY3JlYXNlcw0KcGVyZm9ybWFuY2Ugb2YgZG93bnN0cmVhbSBzdGF0aXN0aWNhbCB0ZXN0cyBhbmQgaW1wcm92ZXMgaW50ZXJwcmV0YXRpb24NCm9mIHRydWUgYmlvbG9naWNhbCBzaWduYWwuDQoNClBsb3QgZGV0ZWN0aW9uIHJhdGUNCg0KYGBge3IgcGxvdF9kZXRfcmF0ZX0NCnBsb3RfZGV0ZWN0IDwtIGRhdGEuZnJhbWUoRnJlcSA9IGMoMSwgNSwgMTAsIDIwLCAzMCwgNTApKQ0KcGxvdF9kZXRlY3QkTnVtYmVyIDwtDQogIHVubGlzdChsYXBwbHkoYygwLjAxLCAwLjA1LCAwLjEsIDAuMiwgMC4zLCAwLjUpLA0KICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIHtzdW0oZkRhdGEodGFyZ2V0X0RhdGEpJERldGVjdGlvblJhdGUgPj0geCl9KSkNCnBsb3RfZGV0ZWN0JFJhdGUgPC0gcGxvdF9kZXRlY3QkTnVtYmVyIC8gbnJvdyhmRGF0YSh0YXJnZXRfRGF0YSkpDQpyb3duYW1lcyhwbG90X2RldGVjdCkgPC0gcGxvdF9kZXRlY3QkRnJlcQ0KDQpnZ3Bsb3QocGxvdF9kZXRlY3QsIGFlcyh4ID0gYXMuZmFjdG9yKEZyZXEpLCB5ID0gUmF0ZSwgZmlsbCA9IFJhdGUpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBmb3JtYXRDKE51bWJlciwgZm9ybWF0ID0gImQiLCBiaWcubWFyayA9ICIsIikpLA0KICAgICAgICAgICAgdmp1c3QgPSAxLjYsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDQpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gIm9yYW5nZTIiLCBtaWQgPSAibGlnaHRibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgaGlnaCA9ICJkb2RnZXJibHVlMyIsIG1pZHBvaW50ID0gMC42NSwNCiAgICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLDEpLA0KICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsNCiAgdGhlbWVfYncoKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsIGxpbWl0cyA9IGMoMCwxKSwNCiAgICAgICAgICAgICAgICAgICAgIGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLCAwKSkpICsNCiAgbGFicyh4ID0gIiUgb2YgU2VnbWVudHMiLA0KICAgICAgIHkgPSAiR2VuZXMgRGV0ZWN0ZWQsICUgb2YgUGFuZWwgPiBMT1EiKQ0KYGBgDQoNClN1YnNldCB0byB0YXJnZXQgZ2VuZXMgZGV0ZWN0ZWQgaW4gYXQgbGVhc3QgMTAlIG9mIHRoZSBzYW1wbGVzLiBBbHNvDQptYW51YWxseSBpbmNsdWRlIHRoZSBuZWdhdGl2ZSBjb250cm9sIHByb2JlLCBmb3IgZG93bnN0cmVhbSB1c2UNCg0KYGBge3Igc3Vic2V0X3RvXzEwcF9kZXRlY3RlZF9nZW5lc30NCiMgZGVmYXVsdD0wLjENCm5lZ2F0aXZlUHJvYmVmRGF0YSA8LSBzdWJzZXQoZkRhdGEodGFyZ2V0X0RhdGEpLCBDb2RlQ2xhc3MgPT0gIk5lZ2F0aXZlIikNCm5lZ19wcm9iZXMgPC0gdW5pcXVlKG5lZ2F0aXZlUHJvYmVmRGF0YSRUYXJnZXROYW1lKQ0KdGFyZ2V0X0RhdGEgPC0gDQogIHRhcmdldF9EYXRhW2ZEYXRhKHRhcmdldF9EYXRhKSREZXRlY3Rpb25SYXRlID49IDAuMDUgfA0KICAgICAgICAgICAgICAgICAgICBmRGF0YSh0YXJnZXRfRGF0YSkkVGFyZ2V0TmFtZSAlaW4lIG5lZ19wcm9iZXMsIF0NCg0KIyByZXRhaW4gb25seSBkZXRlY3RlZCBnZW5lcyBvZiBpbnRlcmVzdA0KZ29pIDwtIGdvaVtnb2kgJWluJSByb3duYW1lcyh0YXJnZXRfRGF0YSldDQpgYGANCg0KIyA1IE5vcm1hbGl6YXRpb24NCg0KV2Ugd2lsbCBub3cgbm9ybWFsaXplIHRoZSBHZW9NeCBkYXRhIGZvciBkb3duc3RyZWFtIHZpc3VhbGl6YXRpb25zIGFuZA0KZGlmZmVyZW50aWFsIGV4cHJlc3Npb24uIFRoZSB0d28gY29tbW9uIG1ldGhvZHMgZm9yIG5vcm1hbGl6YXRpb24gb2YNCkRTUC1OR1MgUk5BIGRhdGEgYXJlIGkpIHF1YXJ0aWxlIDMgKFEzKSBvciBpaSkgYmFja2dyb3VuZCBub3JtYWxpemF0aW9uLg0KDQpCb3RoIG9mIHRoZXNlIG5vcm1hbGl6YXRpb24gbWV0aG9kcyBlc3RpbWF0ZSBhIG5vcm1hbGl6YXRpb24gZmFjdG9yIHBlcg0Kc2VnbWVudCB0byBicmluZyB0aGUgc2VnbWVudCBkYXRhIGRpc3RyaWJ1dGlvbnMgdG9nZXRoZXIuIE1vcmUgYWR2YW5jZWQNCm1ldGhvZHMgZm9yIG5vcm1hbGl6YXRpb24gYW5kIG1vZGVsaW5nIGFyZSB1bmRlciBhY3RpdmUgZGV2ZWxvcG1lbnQuDQpIb3dldmVyLCBmb3IgbW9zdCBzdHVkaWVzLCB0aGVzZSBtZXRob2RzIGFyZSBzdWZmaWNpZW50IGZvcg0KdW5kZXJzdGFuZGluZyBkaWZmZXJlbmNlcyBiZXR3ZWVuIGJpb2xvZ2ljYWwgY2xhc3NlcyBvZiBzZWdtZW50cyBhbmQNCnNhbXBsZXMuDQoNClEzIG5vcm1hbGl6YXRpb24gaXMgdHlwaWNhbGx5IHRoZSBwcmVmZXJyZWQgbm9ybWFsaXphdGlvbiBzdHJhdGVneSBmb3INCm1vc3QgRFNQLU5HUyBSTkEgc3R1ZGllcy4gR2l2ZW4gdGhlIGxvdyBuZWdhdGl2ZSBwcm9iZSBjb3VudHMgaW4gdGhpcw0KcGFydGljdWxhciBkYXRhc2V0IGFzIHNob3duIGR1cmluZyBTZWdtZW50IFFDLCB3ZSB3b3VsZCBmdXJ0aGVyIGF2b2lkDQpiYWNrZ3JvdW5kIG5vcm1hbGl6YXRpb24gYXMgaXQgbWF5IGJlIGxlc3Mgc3RhYmxlLg0KDQpCZWZvcmUgbm9ybWFsaXphdGlvbiwgd2Ugd2lsbCBleHBsb3JlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgdXBwZXINCnF1YXJ0aWxlIChRMykgb2YgdGhlIGNvdW50cyBpbiBlYWNoIHNlZ21lbnQgd2l0aCB0aGUgZ2VvbWV0cmljIG1lYW4gb2YNCnRoZSBuZWdhdGl2ZSBjb250cm9sIHByb2JlcyBpbiB0aGUgZGF0YS4gSWRlYWxseSwgdGhlcmUgc2hvdWxkIGJlIGENCnNlcGFyYXRpb24gYmV0d2VlbiB0aGVzZSB0d28gdmFsdWVzIHRvIGVuc3VyZSB3ZSBoYXZlIHN0YWJsZSBtZWFzdXJlIG9mDQpRMyBzaWduYWwuIElmIHlvdSBkbyBub3Qgc2VlIHN1ZmZpY2llbnQgc2VwYXJhdGlvbiBiZXR3ZWVuIHRoZXNlIHZhbHVlcywNCnlvdSBtYXkgY29uc2lkZXIgbW9yZSBhZ2dyZXNzaXZlIGZpbHRlcmluZyBvZiBsb3cgc2lnbmFsIHNlZ21lbnRzL2dlbmVzLg0KDQpHcmFwaCBRMyB2YWx1ZSB2cyBuZWdHZW9NZWFuIG9mIE5lZ2F0aXZlcw0KDQpgYGB7ciBscG90X3EzX25lZ0dlb01lYW59DQphbm5fb2ZfaW50ZXJlc3QgPC0gIkFOTjMiDQpTdGF0X2RhdGEgPC0gDQogIGRhdGEuZnJhbWUocm93Lm5hbWVzID0gY29sbmFtZXMoZXhwcnModGFyZ2V0X0RhdGEpKSwNCiAgICAgICAgICAgICBTZWdtZW50ID0gY29sbmFtZXMoZXhwcnModGFyZ2V0X0RhdGEpKSwNCiAgICAgICAgICAgICBBbm5vdGF0aW9uID0gcERhdGEodGFyZ2V0X0RhdGEpWywgYW5uX29mX2ludGVyZXN0XSwNCiAgICAgICAgICAgICBRMyA9IHVubGlzdChhcHBseShleHBycyh0YXJnZXRfRGF0YSksIDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVhbnRpbGUsIDAuNzUsIG5hLnJtID0gVFJVRSkpLA0KICAgICAgICAgICAgIE5lZ1Byb2JlID0gZXhwcnModGFyZ2V0X0RhdGEpW25lZ19wcm9iZXMsIF0pDQpTdGF0X2RhdGFfbSA8LSBtZWx0KFN0YXRfZGF0YSwgbWVhc3VyZS52YXJzID0gYygiUTMiLCAiTmVnUHJvYmUiKSwNCiAgICAgICAgICAgICAgICAgICAgdmFyaWFibGUubmFtZSA9ICJTdGF0aXN0aWMiLCB2YWx1ZS5uYW1lID0gIlZhbHVlIikNCg0KcGx0MSA8LSBnZ3Bsb3QoU3RhdF9kYXRhX20sDQogICAgICAgICAgICAgICBhZXMoeCA9IFZhbHVlLCBmaWxsID0gU3RhdGlzdGljKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNDApICsgdGhlbWVfYncoKSArDQogIHNjYWxlX3hfY29udGludW91cyh0cmFucyA9ICJsb2cyIikgKw0KICBmYWNldF93cmFwKH5Bbm5vdGF0aW9uLCBucm93ID0gMSkgKyANCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9IDMsIHR5cGUgPSAicXVhbCIpICsNCiAgbGFicyh4ID0gIkNvdW50cyIsIHkgPSAiU2VnbWVudHMsICMiKQ0KDQpwbHQyIDwtIGdncGxvdChTdGF0X2RhdGEsDQogICAgICAgICAgICAgICBhZXMoeCA9IE5lZ1Byb2JlLCB5ID0gUTMsIGNvbG9yID0gQW5ub3RhdGlvbikpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBsdHkgPSAiZGFzaGVkIiwgY29sb3IgPSAiZGFya2dyYXkiKSArDQogIGdlb21fcG9pbnQoKSArIGd1aWRlcyhjb2xvciA9ICJub25lIikgKyB0aGVtZV9idygpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zID0gImxvZzIiKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMiIpICsNCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKw0KICBsYWJzKHggPSAiTmVnYXRpdmUgUHJvYmUgR2VvTWVhbiwgQ291bnRzIiwgeSA9ICJRMyBWYWx1ZSwgQ291bnRzIikNCg0KcGx0MyA8LSBnZ3Bsb3QoU3RhdF9kYXRhLA0KICAgICAgICAgICAgICAgYWVzKHggPSBOZWdQcm9iZSwgeSA9IFEzIC8gTmVnUHJvYmUsIGNvbG9yID0gQW5ub3RhdGlvbikpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgbHR5ID0gImRhc2hlZCIsIGNvbG9yID0gImRhcmtncmF5IikgKw0KICBnZW9tX3BvaW50KCkgKyB0aGVtZV9idygpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zID0gImxvZzIiKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMiIpICsNCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKw0KICBsYWJzKHggPSAiTmVnYXRpdmUgUHJvYmUgR2VvTWVhbiwgQ291bnRzIiwgeSA9ICJRMy9OZWdQcm9iZSBWYWx1ZSwgQ291bnRzIikNCg0KYnRtX3JvdyA8LSBwbG90X2dyaWQocGx0MiwgcGx0MywgbnJvdyA9IDEsIGxhYmVscyA9IGMoIkIiLCAiIiksDQogICAgICAgICAgICAgICAgICAgICByZWxfd2lkdGhzID0gYygwLjQzLDAuNTcpKQ0KcGxvdF9ncmlkKHBsdDEsIGJ0bV9yb3csIG5jb2wgPSAxLCBsYWJlbHMgPSBjKCJBIiwgIiIpKQ0KDQpgYGANCg0KUTMgbm9ybSAoNzV0aCBwZXJjZW50aWxlKSBmb3IgV1RBL0NUQSB3aXRoIG9yIHdpdGhvdXQgY3VzdG9tIHNwaWtlLWlucw0KDQpgYGB7ciBxM19ub3JtfQ0KdGFyZ2V0X0RhdGEgPC0gbm9ybWFsaXplKHRhcmdldF9EYXRhICwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm9ybV9tZXRob2QgPSAicXVhbnQiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVzaXJlZFF1YW50aWxlID0gLjc1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b0VsdCA9ICJxX25vcm0iKQ0KIywgZGF0YV90eXBlID0gIlJOQSIgZGVwcmljYXRlZCBhZnRlciA0LjENCg0KYGBgDQoNCkJhY2tncm91bmQgbm9ybWFsaXphdGlvbiBmb3IgV1RBL0NUQSB3aXRob3V0IGN1c3RvbSBzcGlrZS1pbg0KDQpgYGB7ciBiZ19ub3JtfQ0KdGFyZ2V0X0RhdGEgPC0gbm9ybWFsaXplKHRhcmdldF9EYXRhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtX21ldGhvZCA9ICJuZWciLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbUVsdCA9ICJleHBycyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvRWx0ID0gIm5lZ19ub3JtIikNCg0KIyAsIGRhdGFfdHlwZSA9ICJSTkEiIGRlcHJpY2F0ZWQgYWZ0ZXIgNC4xDQpgYGANCg0KIyA1LjEgVmlzdWFsaXplIHRoZSBmaXJzdCAxMCBzZWdtZW50cyB3aXRoIGVhY2ggbm9ybWFsaXphdGlvbiBtZXRob2Qgey50YWJzZXQgLnRhYnNldC1waWxsc30NCg0KVXNlIHRoZSB0YWItbWVudSB0byBuYXZpZ2F0ZSENCg0KYGBge3IgdmlzdWxhaXplX25vcm1zfQ0KDQojRml4IHplcm8gdmFsdWVzLCB3aGljaCBnbyB0byAtaW5mIGluIGxvZyB0cmFuc2Zvcm0gaW4gc3RhbmRhcmQgYm94cGxvdA0KIyB0ZW1wIDwtYXMubWF0cml4KGV4cHJzKCh0YXJnZXRfRGF0YSlbLDE6MTBdKSkNCiMgbG9uZyA8LSBtZWx0KHRlbXApDQojIGNvbG5hbWVzKGxvbmcpIDwtIGMoImdlbmUiLCJzZWdtZW50IiwiY291bnQiKQ0KIyBnZ3Bsb3QobG9uZywgYWVzKHg9c2VnbWVudCx5PWNvdW50KSkgKw0KIyAgICAgZ2VvbV9ib3hwbG90KGZpbGw9IiM5RURBRTUiKSArDQojICAgICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9c2NhbGVzOjpwc2V1ZG9fbG9nX3RyYW5zKGJhc2UgPSAxMCkpICsNCiMgICAgIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPWMoMToxMCkpICsNCiMgICAgIGxhYnModGl0bGU9IlJhdyBjb3VudHMiLCB4PSJzZWdtZW50IiwgeSA9ICJDb3VudHMsIFJhdyIpDQojIA0KIyANCiMgdGVtcCA8LWFzLm1hdHJpeChhc3NheURhdGFFbGVtZW50KHRhcmdldF9EYXRhWywxOjEwXSwgZWx0ID0gInFfbm9ybSIpKQ0KIyBsb25nIDwtIG1lbHQodGVtcCkNCiMgY29sbmFtZXMobG9uZykgPC0gYygiZ2VuZSIsInNlZ21lbnQiLCJjb3VudCIpDQojIGdncGxvdChsb25nLCBhZXMoeD1zZWdtZW50LHk9Y291bnQpKSArDQojICAgICBnZW9tX2JveHBsb3QoZmlsbCA9ICIjMkNBMDJDIikgKw0KIyAgICAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zPXNjYWxlczo6cHNldWRvX2xvZ190cmFucyhiYXNlID0gMTApKSArDQojICAgICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscz1jKDE6MTApKSArDQojICAgICBsYWJzKHRpdGxlPSJRMyBOb3JtIENvdW50cyIsIHg9InNlZ21lbnQiLCB5ID0gIkNvdW50cywgUTMgTm9ybWFsaXplZCIpDQojIA0KIyANCiMgdGVtcCA8LWFzLm1hdHJpeChhc3NheURhdGFFbGVtZW50KHRhcmdldF9EYXRhWywxOjEwXSwgZWx0ID0gIm5lZ19ub3JtIikpDQojIGxvbmcgPC0gbWVsdCh0ZW1wKQ0KIyBjb2xuYW1lcyhsb25nKSA8LSBjKCJnZW5lIiwic2VnbWVudCIsImNvdW50IikNCiMgZ2dwbG90KGxvbmcsIGFlcyh4PXNlZ21lbnQseT1jb3VudCkpICsNCiMgICAgIGdlb21fYm94cGxvdChmaWxsID0gIiNGRjdGMEUiKSArDQojICAgICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9c2NhbGVzOjpwc2V1ZG9fbG9nX3RyYW5zKGJhc2UgPSAxMCkpICsNCiMgICAgIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPWMoMToxMCkpICsNCiMgICAgIGxhYnModGl0bGU9Ik5lZyBOb3JtIENvdW50cyIsIHg9InNlZ21lbnQiLCB5ID0gIkNvdW50cywgTmVnLiBOb3JtYWxpemVkIikNCmBgYA0KDQojIyByYXcgY291bnRzDQoNCmBgYHtyfQ0KYm94cGxvdChleHBycyh0YXJnZXRfRGF0YSlbLDE6OF0sDQogICAgICAgIGNvbCA9ICIjOUVEQUU1IiwgbWFpbiA9ICJSYXcgQ291bnRzIiwNCiAgICAgICAgbG9nID0gInkiLCBuYW1lcyA9IDE6OCwgeGxhYiA9ICJTZWdtZW50IiwNCiAgICAgICAgeWxhYiA9ICJDb3VudHMsIFJhdyIpDQpgYGANCg0KIyMgUTMgbm9ybWFsaXplZCB7LmFjdGl2ZX0NCg0KYGBge3J9DQpib3hwbG90KGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGFbLDE6OF0sIGVsdCA9ICJxX25vcm0iKSwNCiAgICAgICAgY29sID0gIiMyQ0EwMkMiLCBtYWluID0gIlEzIE5vcm0gQ291bnRzIiwNCiAgICAgICAgbG9nID0gInkiLCBuYW1lcyA9IDE6OCwgeGxhYiA9ICJTZWdtZW50IiwNCiAgICAgICAgeWxhYiA9ICJDb3VudHMsIFEzIE5vcm1hbGl6ZWQiKQ0KYGBgDQoNCiMjIE5lZ2F0aXZlIHByb2JlIG5vcm1hbGl6YXRpb24NCg0KYGBge3J9DQpib3hwbG90KGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGFbLDE6OF0sIGVsdCA9ICJuZWdfbm9ybSIpLA0KICAgICAgICBjb2wgPSAiI0ZGN0YwRSIsIG1haW4gPSAiTmVnIE5vcm0gQ291bnRzIiwNCiAgICAgICAgbG9nID0gInkiLCBuYW1lcyA9IDE6OCwgeGxhYiA9ICJTZWdtZW50IiwNCiAgICAgICAgeWxhYiA9ICJDb3VudHMsIE5lZy4gTm9ybWFsaXplZCIpDQoNCmBgYA0KDQojIDYgVW5zdXBlcnZpc2VkIEFuYWx5c2lzDQoNCiMgNi4xIFVNQVAgey50YWJzZXQgLnRhYnNldC1waWxsc30NCg0KVXNlIHRoZSB0YWItbWVudSB0byBuYXZpZ2F0ZSENCg0KIyMgMQ0KDQpgYGB7ciB1bWFwLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD04fQ0KZ2MoKQ0KY3VzdG9tX3VtYXAgPC0gdW1hcDo6dW1hcC5kZWZhdWx0cw0KY3VzdG9tX3VtYXAkcmFuZG9tX3N0YXRlIDwtIDQyDQojIHJ1biBVTUFQDQoNCnVtYXBfb3V0IDwtDQogIHVtYXAodChsb2cyKGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGEgLCBlbHQgPSAicV9ub3JtIikpKSwNCiAgICAgICBjb25maWcgPSBjdXN0b21fdW1hcCkNCnBEYXRhKHRhcmdldF9EYXRhKVssIGMoIlVNQVAxIiwgIlVNQVAyIildIDwtIHVtYXBfb3V0JGxheW91dFssIGMoMSwyKV0NCmdncGxvdChwRGF0YSh0YXJnZXRfRGF0YSksDQogICAgICAgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IEFOTjEsIHNoYXBlID0gc2xpZGVfbmFtZSkpICsNCg0KICBnZW9tX3BvaW50KHNpemUgPSAzKSArDQogICNnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPXJvdy5uYW1lcyhwRGF0YSh0YXJnZXRfRGF0YSkpKSwgc2l6ZT0yLG1heC5vdmVybGFwcyA9IDEwMCkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIyAyDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD04fQ0KdW1hcF9vdXQgPC0NCiAgdW1hcCh0KGxvZzIoYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YSAsIGVsdCA9ICJxX25vcm0iKSkpLA0KICAgICAgIGNvbmZpZyA9IGN1c3RvbV91bWFwKQ0KcERhdGEodGFyZ2V0X0RhdGEpWywgYygiVU1BUDEiLCAiVU1BUDIiKV0gPC0gdW1hcF9vdXQkbGF5b3V0WywgYygxLDIpXQ0KZ2dwbG90KHBEYXRhKHRhcmdldF9EYXRhKSwNCiAgICAgICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gQU5OMiwgc2hhcGUgPSBBTk4xKSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAzKSArDQogICNnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPXJvdy5uYW1lcyhwRGF0YSh0YXJnZXRfRGF0YSkpKSwgc2l6ZT0yLG1heC5vdmVybGFwcyA9IDEwMCkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIyAzDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD04fQ0KdW1hcF9vdXQgPC0NCiAgdW1hcCh0KGxvZzIoYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YSAsIGVsdCA9ICJxX25vcm0iKSkpLA0KICAgICAgIGNvbmZpZyA9IGN1c3RvbV91bWFwKQ0KcERhdGEodGFyZ2V0X0RhdGEpWywgYygiVU1BUDEiLCAiVU1BUDIiKV0gPC0gdW1hcF9vdXQkbGF5b3V0WywgYygxLDIpXQ0KZ2dwbG90KHBEYXRhKHRhcmdldF9EYXRhKSwNCiAgICAgICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gQU5OMywgc2hhcGUgPSBBTk4yKSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAzKSArDQogICNnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPXJvdy5uYW1lcyhwRGF0YSh0YXJnZXRfRGF0YSkpKSwgc2l6ZT0yLG1heC5vdmVybGFwcyA9IDEwMCkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIDYuMSBSdW4gdFNORSB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQpVc2UgdGhlIHRhYi1tZW51IHRvIG5hdmlnYXRlIQ0KDQpPbmUgY29tbW9uIGFwcHJvYWNoIHRvIHVuZGVyc3RhbmRpbmcgaGlnaC1wbGV4IGRhdGEgaXMgZGltZW5zaW9uDQpyZWR1Y3Rpb24uIFR3byBjb21tb24gbWV0aG9kcyBhcmUgVU1BUCBhbmQgdFNORSwgd2hpY2ggYXJlDQpub24tb3J0aG9nb25hbGx5IGNvbnN0cmFpbmVkIHByb2plY3Rpb25zIHRoYXQgY2x1c3RlciBzYW1wbGVzIGJhc2VkIG9uDQpvdmVyYWxsIGdlbmUgZXhwcmVzc2lvbi4gSW4gdGhpcyBzdHVkeSwgd2Ugc2VlIGJ5IGVpdGhlciBVTUFQIChmcm9tIHRoZQ0KdW1hcCBwYWNrYWdlKSBvciB0U05FIChmcm9tIHRoZSBSdHNuZSBwYWNrYWdlKQ0KDQojIyAxDQoNCmBgYHtyIHRTTkUsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTh9DQpzZXQuc2VlZCg0MikgIyBzZXQgdGhlIHNlZWQgZm9yIHRTTkUgYXMgd2VsbA0KDQp0c25lX291dCA8LQ0KICBSdHNuZSh0KGxvZzIoYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YSAsIGVsdCA9ICJxX25vcm0iKSkpLA0KICAgICAgICBwZXJwbGV4aXR5ID0gbmNvbCh0YXJnZXRfRGF0YSkqLjE1KQ0KcERhdGEodGFyZ2V0X0RhdGEpWywgYygidFNORTEiLCAidFNORTIiKV0gPC0gdHNuZV9vdXQkWVssIGMoMSwyKV0NCmdncGxvdChwRGF0YSh0YXJnZXRfRGF0YSksDQogICAgICAgYWVzKHggPSB0U05FMSwgeSA9IHRTTkUyLCBzaGFwZSA9IEFOTjMsIGNvbG9yID0gQU5OMSkpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMykgKw0KICBnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPXJvdy5uYW1lcyhwRGF0YSh0YXJnZXRfRGF0YSkpKSwgc2l6ZT0yLG1heC5vdmVybGFwcyA9IDEwMCkrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIyAyDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD04fQ0KdHNuZV9vdXQgPC0NCiAgUnRzbmUodChsb2cyKGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGEgLCBlbHQgPSAicV9ub3JtIikpKSwNCiAgICAgICAgcGVycGxleGl0eSA9IG5jb2wodGFyZ2V0X0RhdGEpKi4xNSkNCnBEYXRhKHRhcmdldF9EYXRhKVssIGMoInRTTkUxIiwgInRTTkUyIildIDwtIHRzbmVfb3V0JFlbLCBjKDEsMildDQpnZ3Bsb3QocERhdGEodGFyZ2V0X0RhdGEpLA0KICAgICAgIGFlcyh4ID0gdFNORTEsIHkgPSB0U05FMiwgY29sb3IgPSBBTk4yLCBzaGFwZSA9IEFOTjEpKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDMpICsNCiAgI2dlb21fdGV4dF9yZXBlbChhZXMobGFiZWw9cm93Lm5hbWVzKHBEYXRhKHRhcmdldF9EYXRhKSkpLCBzaXplPTIsbWF4Lm92ZXJsYXBzID0gMTAwKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCiMjIDMNCg0KYGBge3IsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTh9DQp0c25lX291dCA8LQ0KICBSdHNuZSh0KGxvZzIoYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YSAsIGVsdCA9ICJxX25vcm0iKSkpLA0KICAgICAgICBwZXJwbGV4aXR5ID0gbmNvbCh0YXJnZXRfRGF0YSkqLjE1KQ0KcERhdGEodGFyZ2V0X0RhdGEpWywgYygidFNORTEiLCAidFNORTIiKV0gPC0gdHNuZV9vdXQkWVssIGMoMSwyKV0NCmdncGxvdChwRGF0YSh0YXJnZXRfRGF0YSksDQogICAgICAgYWVzKHggPSB0U05FMSwgeSA9IHRTTkUyLCBjb2xvciA9IEFOTjMsIHNoYXBlID0gQU5OMikpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMykgKw0KICAjZ2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbD1yb3cubmFtZXMocERhdGEodGFyZ2V0X0RhdGEpKSksIHNpemU9MixtYXgub3ZlcmxhcHMgPSAxMDApKw0KICB0aGVtZV9idygpDQpgYGANCg0KIyA2LjIgQ2x1c3RlcmluZyBoaWdoIENWIEdlbmVzIHsudGFic2V0IC50YWJzZXQtcGlsbHN9DQoNCkFub3RoZXIgYXBwcm9hY2ggdG8gZXhwbG9yZSB0aGUgZGF0YSBpcyB0byBjYWxjdWxhdGUgdGhlIGNvZWZmaWNpZW50IG9mDQp2YXJpYXRpb24gKCRDViQpIGZvciBlYWNoIGdlbmUgKCRnJCkgdXNpbmcgdGhlIGZvcm11bGENCiRDVl9nPVNEX2cvbWVhbl9nJC4gV2UgdGhlbiBpZGVudGlmeSBnZW5lcyB3aXRoIGhpZ2ggQ1ZzIHRoYXQgc2hvdWxkDQpoYXZlIGxhcmdlIGRpZmZlcmVuY2VzIGFjcm9zcyB0aGUgdmFyaW91cyBwcm9maWxlZCBzZWdtZW50cy4gVGhpcw0KdW5iaWFzZWQgYXBwcm9hY2ggY2FuIHJldmVhbCBoaWdobHkgdmFyaWFibGUgZ2VuZXMgYWNyb3NzIHRoZSBzdHVkeS4NCg0KV2UgcGxvdCB0aGUgcmVzdWx0cyB1c2luZyB1bnN1cGVydmlzZWQgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsDQpkaXNwbGF5ZWQgYXMgYSBoZWF0bWFwLg0KDQpgYGB7ciBjbHVzdGVyaW5nMX0NCiMgY3JlYXRlIGEgbG9nMiB0cmFuc2Zvcm0gb2YgdGhlIGRhdGEgZm9yIGFuYWx5c2lzDQphc3NheURhdGFFbGVtZW50KG9iamVjdCA9IHRhcmdldF9EYXRhLCBlbHQgPSAibG9nX3EiKSA8LQ0KICBhc3NheURhdGFBcHBseSh0YXJnZXRfRGF0YSwgMiwgRlVOID0gbG9nLCBiYXNlID0gMiwgZWx0ID0gInFfbm9ybSIpDQoNCiMgY3JlYXRlIENWIGZ1bmN0aW9uDQpjYWxjX0NWIDwtIGZ1bmN0aW9uKHgpIHtzZCh4KSAvIG1lYW4oeCl9DQpDVl9kYXQgPC0gYXNzYXlEYXRhQXBwbHkodGFyZ2V0X0RhdGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgZWx0ID0gImxvZ19xIiwgTUFSR0lOID0gMSwgY2FsY19DVikNCiMgc2hvdyB0aGUgaGlnaGVzdCBDRCBnZW5lcyBhbmQgdGhlaXIgQ1YgdmFsdWVzDQpzb3J0KENWX2RhdCwgZGVjcmVhc2luZyA9IFRSVUUpWzE6NV0NCmBgYA0KDQojIyBUYWJsZSBvZiBDViB2YWx1ZXMNCg0KYGBge3J9DQojIHNob3cgdGhlIGhpZ2hlc3QgQ0QgZ2VuZXMgYW5kIHRoZWlyIENWIHZhbHVlcw0KZGF0YXRhYmxlKGFzLmRhdGEuZnJhbWUoQ1ZfZGF0KSwNCiAgICAgICAgICBleHRlbnNpb25zID0gJ0J1dHRvbnMnLCBvcHRpb25zID0gbGlzdCAoDQogICAgICAgICAgICBvcmRlciA9IGxpc3QoMSwgJ2Rlc2MnKSwNCiAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgYnV0dG9ucyA9IGMoJ2NvcHknLCAnY3N2JywgJ2V4Y2VsJywgJ3BkZicsICdwcmludCcpDQogICAgICAgICAgKSwgY2FwdGlvbiA9ICJDViB2YWx1ZXMgb2YgZ2VuZXMiIA0KKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJDVl9kYXQiKSwgZGlnaXRzPTMpDQoNCmBgYA0KDQojIyBIZWF0bWFwIGdlbmVzIGluIHRoZSB0b3AgM3JkIG9mIHRoZSBDViB2YWx1ZXMgey5hY3RpdmV9DQoNCmBgYHtyIENWaGVhdG1hcCwgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MTV9DQpHT0kgPC0gbmFtZXMoQ1ZfZGF0KVtDVl9kYXQgPiBxdWFudGlsZShDVl9kYXQsIDAuNzUpXQ0KDQpwaGVhdG1hcChhc3NheURhdGFFbGVtZW50KHRhcmdldF9EYXRhW0dPSSwgXSwgZWx0ID0gImxvZ19xIiksDQogICAgICAgICBzY2FsZSA9ICJyb3ciLA0KICAgICAgICAgY3V0cmVlX2NvbHMgPSAzLA0KICAgICAgICAgY3V0cmVlX3Jvd3MgPSAzLA0KICAgICAgICAgc2hvd19yb3duYW1lcyA9IEZBTFNFLCBzaG93X2NvbG5hbWVzID0gVFJVRSwNCiAgICAgICAgIGJvcmRlcl9jb2xvciA9IE5BLA0KICAgICAgICAgZHJvcF9sZXZlbHMgPSBUUlVFLA0KICAgICAgICAgY2x1c3RlcmluZ19tZXRob2QgPSAiYXZlcmFnZSIsDQogICAgICAgICBjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3MgPSAiY29ycmVsYXRpb24iLA0KICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImNvcnJlbGF0aW9uIiwNCiAgICAgICAgIGJyZWFrcyA9IHNlcSgtMywgMywgMC4wNSksDQogICAgICAgICBjb2xvciA9IGNvbG9yUmFtcFBhbGV0dGUoYygicHVycGxlMyIsICJibGFjayIsICJ5ZWxsb3cyIikpKDEyMCksDQogICAgICAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGNvbG9yX2xpc3QsDQogICAgICAgICBhbm5vdGF0aW9uX2NvbCA9IHBEYXRhKHRhcmdldF9EYXRhKVssIGFubl9uYW1lc10pDQoNCmBgYA0KDQpgYGB7ciBsb2dfdHJhbnNmb3JtfQ0KYXNzYXlEYXRhRWxlbWVudChvYmplY3QgPSB0YXJnZXRfRGF0YSwgZWx0ID0gImxvZ19xIikgPC0gIGFzc2F5RGF0YUFwcGx5KHRhcmdldF9EYXRhLCAyLCBGVU4gPSBsb2csIGJhc2UgPSAyLCBlbHQgPSAicV9ub3JtIikNCmxvZ19xIDwtYXMuZGF0YS5mcmFtZShhc3NheURhdGFFbGVtZW50KHRhcmdldF9EYXRhLCBlbHQ9ICJsb2dfcSIpKQ0KYGBgDQoNCiMgNi4yLjAgQ3JlYXRlIHN1YnNldCBvZiBkYXRhDQoNCmBgYHtyIGRlZmluZV9hY3RpdmVfYW9pc30NCiMgZGV0ZXJtaW5lIEFPSXMgdG8gdXNlDQojYWN0aXZlX2FvaXM8LXJvd25hbWVzKGFubilbYW5uJHBhdGllbnQ9PSJwNCJdDQphY3RpdmVfYW9pczwtcm93bmFtZXMoYW5uKQ0KDQpgYGANCg0KIyA2LjIuMSBDbHVzdGVyaW5nIGhpZ2ggQ1YgZ2VuZXMgZm9yIHN1YnNldCB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQpDYWxjdWxhdGluZyBDViB2YWx1ZXMNCg0KYGBge3IgY2x1c3RlcmluZ19zdWJzZXQsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTE1fQ0KIyBjcmVhdGUgYSBsb2cyIHRyYW5zZm9ybSBvZiB0aGUgZGF0YSBmb3IgYW5hbHlzaXMNCmFzc2F5RGF0YUVsZW1lbnQob2JqZWN0ID0gdGFyZ2V0X0RhdGEsIGVsdCA9ICJsb2dfcSIpIDwtDQogIGFzc2F5RGF0YUFwcGx5KHRhcmdldF9EYXRhLCAyLCBGVU4gPSBsb2csIGJhc2UgPSAyLCBlbHQgPSAicV9ub3JtIikNCg0KIyBjcmVhdGUgQ1YgZnVuY3Rpb24NCmNhbGNfQ1YgPC0gZnVuY3Rpb24oeCkge3NkKHgpIC8gbWVhbih4KX0NCkNWX2RhdCA8LSBhc3NheURhdGFBcHBseSh0YXJnZXRfRGF0YVssYWN0aXZlX2FvaXNdLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGVsdCA9ICJsb2dfcSIsIE1BUkdJTiA9IDEsIGNhbGNfQ1YpDQoNCmBgYA0KDQojIyBUYWJsZSBvZiBDViB2YWx1ZXMNCg0KYGBge3J9DQojIHNob3cgdGhlIGhpZ2hlc3QgQ0QgZ2VuZXMgYW5kIHRoZWlyIENWIHZhbHVlcw0KZGF0YXRhYmxlKGFzLmRhdGEuZnJhbWUoQ1ZfZGF0KSwNCiAgICAgICAgICBleHRlbnNpb25zID0gJ0J1dHRvbnMnLCBvcHRpb25zID0gbGlzdCAoDQogICAgICAgICAgICBvcmRlciA9IGxpc3QoMSwgJ2Rlc2MnKSwNCiAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgYnV0dG9ucyA9IGMoJ2NvcHknLCAnY3N2JywgJ2V4Y2VsJywgJ3BkZicsICdwcmludCcpDQogICAgICAgICAgKSwgY2FwdGlvbiA9ICJDViB2YWx1ZXMgb2YgZ2VuZXMiIA0KKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJDVl9kYXQiKSwgZGlnaXRzPTMpDQoNCmBgYA0KDQojIyBIZWF0bWFwIG9uIG9mIHN1YnNldCwgZ2VuZXMgaW4gdGhlIHRvcCAzcmQgb2YgdGhlIENWIHZhbHVlcyB7LmFjdGl2ZX0NCg0KYGBge3IsIGZpZy53aWR0aD0yMCxmaWcuaGVpZ2h0PTE1fQ0KIyBJZGVudGlmeSBnZW5lcyBpbiB0aGUgdG9wIDNyZCBvZiB0aGUgQ1YgdmFsdWVzDQpHT0kgPC0gbmFtZXMoQ1ZfZGF0KVtDVl9kYXQgPiBxdWFudGlsZShDVl9kYXQsIDAuNzUpXQ0KcGhlYXRtYXAoYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YVtHT0ksYWN0aXZlX2FvaXMgXSwgZWx0ID0gImxvZ19xIiksDQogICAgICAgIHNjYWxlID0gInJvdyIsDQogICAgICAgIGZvbnRzaXplX3JvdyA9IDUsDQogICAgICAgIGN1dHJlZV9jb2xzID0gMywNCiAgICAgICAgY3V0cmVlX3Jvd3MgPSAzLA0KICAgICAgICBzaG93X3Jvd25hbWVzID0gRkFMU0UsIHNob3dfY29sbmFtZXMgPSBUUlVFLA0KICAgICAgICBib3JkZXJfY29sb3IgPSBOQSwNCiAgICAgICAgY2x1c3RlcmluZ19tZXRob2QgPSAiYXZlcmFnZSIsDQogICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2VfY29scyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgIGJyZWFrcyA9IHNlcSgtMywgMywgMC4wNSksDQogICAgICAgIGNvbG9yID0gY29sb3JSYW1wUGFsZXR0ZShjKCJwdXJwbGUzIiwgImJsYWNrIiwgInllbGxvdzIiKSkoMTIwKSwNCiAgICAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGNvbG9yX2xpc3QsDQogICAgICAgIGFubm90YXRpb25fY29sID0NCiAgICAgICAgICBwRGF0YSh0YXJnZXRfRGF0YSlbLCBhbm5fbmFtZXNdKQ0KYGBgDQoNCiMgNy4xIERpZmZlcmVudGlhbCBFeHByZXNzaW9uDQoNCiMjIHQtdGVzdA0KDQpgYGB7ciwgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MTAgfQ0KZ2MoKQ0KcGxvdHM8LWxpc3QoKQ0KdGFibGVzPC1saXN0KCkNCmxhYmVsczwtbGlzdCgpDQp0ZXN0PC0idHRlc3QiDQptdGM8LSJCWSINCiNvcHRpb25zOiAiaG9sbSIgICAgICAgImhvY2hiZXJnIiAgICJob21tZWwiICAgICAiYm9uZmVycm9uaSIgIkJIIiAgICAgICAgICJCWSIgICAgICAgICAiZmRyIiANCmNvdW50ZXI9MQ0KDQpjb21wc19kZjwtZGF0YS5mcmFtZShjb21wPScnLHZhbD0nJykNCmZvciAoY2xhc3MgaW4gYygiREtEIiwibm9ybWFsIikpIHsNCmZvciAoYWN0aXZlX2dyb3VwMSBpbiB1bmlxdWUoYW5uJHJlZ2lvbikpIHsNCiAgICBmb3IgKGFjdGl2ZV9ncm91cDIgaW4gdW5pcXVlKGFubiRyZWdpb24pKSB7DQogICAgIA0KICAgICAgI3N1cHJlc3MgcmVkdW5jYW50IGNvbXBhcmVzDQogICAgICBpZihhY3RpdmVfZ3JvdXAxPT1hY3RpdmVfZ3JvdXAyKSB7bmV4dH0NCiAgICAgIGNvbXA8LXBhc3RlKHNvcnQoYyhjbGFzcywgYWN0aXZlX2dyb3VwMSxhY3RpdmVfZ3JvdXAyKSksY29sbGFwc2UgPSAiXyIpDQogICAgICBpZihjb21wICVpbiUgY29tcHNfZGYkY29tcCkge25leHR9DQogICAgICB0ZW1wX2RmPC1kYXRhLmZyYW1lKGNvbXA9Y29tcCAsdmFsPTEpDQogICAgICBjb21wc19kZjwtcmJpbmQoY29tcHNfZGYsdGVtcF9kZikNCiAgICAgIA0KICAgICAgbGFiZWxzW1tjb3VudGVyXV08LXBhc3RlKGFjdGl2ZV9ncm91cDEsIiB2cyAiLCBhY3RpdmVfZ3JvdXAyKQ0KICAgICAgZ3JvdXAxPC1sb2dfcVsscm93bmFtZXMoYW5uKVthbm4kY2xhc3MgPT0gY2xhc3MgJiBhbm4kcmVnaW9uPT1hY3RpdmVfZ3JvdXAxXV0NCiAgICAgIGdyb3VwMjwtbG9nX3FbLHJvd25hbWVzKGFubilbYW5uJGNsYXNzID09IGNsYXNzICYgYW5uJHJlZ2lvbj09YWN0aXZlX2dyb3VwMl1dDQogICAgICANCiAgICAgICNydW4gdF90ZXN0cyAgDQogICAgICByZXN1bHRzPC1hcy5kYXRhLmZyYW1lICggYXBwbHkobG9nX3EsIDEsIGZ1bmN0aW9uKHgpIHQudGVzdCh4W2NvbG5hbWVzKGdyb3VwMSldLHhbY29sbmFtZXMoZ3JvdXAyKV0pJHAudmFsdWUpICkNCiAgICAgIGNvbG5hbWVzKHJlc3VsdHMpPC0icmF3X3BfdmFsdWUiDQogICAgICANCiAgICAgICNtdWx0aXBsZV90ZXN0aW5nX2NvcnJlY3Rpb24NCiAgICAgIGFkal9wX3ZhbHVlPC0gcC5hZGp1c3QocmVzdWx0cyRyYXdfcF92YWx1ZSxtZXRob2Q9bXRjKQ0KICAgICAgcmVzdWx0czwtY2JpbmQocmVzdWx0cyxhZGpfcF92YWx1ZSkNCiAgICAgIA0KICAgICAgI2NhbGNfZmRyDQogICAgICBGRFI8LSBwLmFkanVzdChyZXN1bHRzJHJhd19wX3ZhbHVlLG1ldGhvZD0iZmRyIikNCiAgICAgIHJlc3VsdHM8LWNiaW5kKHJlc3VsdHMsRkRSKQ0KICAgICAgDQogICAgICAjZm9sZF9jaGFuZ2VzDQogICAgICAjYXMgYmFzZSBkYXRhIGlzIGFscmVhZHkgbG9nIHRyYW5zZm9ybWVkLCBtZWFucyBuZWVkIHRvIGJlIHN1YnRyYWN0ZWQgdG8gZ2V0IEZDIGluIGxvZyBzcGFjZQ0KICAgICAgZmNoYW5nZXM8LWFzLmRhdGEuZnJhbWUoYXBwbHkobG9nX3EsIDEsIGZ1bmN0aW9uKHgpIChtZWFuKHhbY29sbmFtZXMoZ3JvdXAxKV0pIC0gbWVhbih4W2NvbG5hbWVzKGdyb3VwMildKSkpKQ0KICAgICAgY29sbmFtZXMoZmNoYW5nZXMpPC0iRkMiDQogICAgICAjcGFzdGUoIkZDIixhY3RpdmVfZ3JvdXAxLCIgLyAiLGFjdGl2ZV9ncm91cDIpDQogICAgICByZXN1bHRzPC1jYmluZChyZXN1bHRzLGZjaGFuZ2VzKQ0KICAgICAgDQogICAgICAjYWRkIGdlbmVuYW1lcw0KICAgICAgcmVzdWx0cyRHZW5lPC1yb3duYW1lcyhyZXN1bHRzKQ0KICAgICAgDQogICAgICAjc2V0IGNhdGVnb3JpZXMgYmFzZWQgb24gUC12YWx1ZSAmIEZEUiBmb3IgcGxvdHRpbmcNCiAgICAgIHJlc3VsdHMkQ29sb3IgPC0gIk5TIG9yIEZDIDwgMC41Ig0KICAgICAgcmVzdWx0cyRDb2xvcltyZXN1bHRzJGFkal9wX3ZhbHVlIDwgMC4wNV0gPC0gIlAgPCAwLjA1Ig0KICAgICAgcmVzdWx0cyRDb2xvcltyZXN1bHRzJEZEUiA8IDAuMDVdIDwtICJGRFIgPCAwLjA1Ig0KICAgICAgcmVzdWx0cyRDb2xvcltyZXN1bHRzJEZEUiA8IDAuMDAxXSA8LSAiRkRSIDwgMC4wMDEiDQogICAgICByZXN1bHRzJENvbG9yW2FicyhyZXN1bHRzJEZDKSA8IDFdIDwtICJOUyBvciBGQyA8IDEiDQogICAgICByZXN1bHRzJENvbG9yIDwtIGZhY3RvcihyZXN1bHRzJENvbG9yLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiTlMgb3IgRkMgPCAxIiwgIlAgPCAwLjA1IiwgIkZEUiA8IDAuMDUiLCAiRkRSIDwgMC4wMDEiKSkNCiAgICAgIA0KICAgICAgI3Z1bGNhbm9wbG90DQogICAgICANCiAgICAgICMgcGljayB0b3AgZ2VuZXMgZm9yIGVpdGhlciBzaWRlIG9mIHZvbGNhbm8gdG8gbGFiZWwNCiAgICAgICMgb3JkZXIgZ2VuZXMgZm9yIGNvbnZlbmllbmNlOg0KICAgICAgDQogICAgICByZXN1bHRzJGludmVydF9QIDwtICgtbG9nMTAocmVzdWx0cyRhZGpfcF92YWx1ZSkpICogc2lnbihyZXN1bHRzJEZDKQ0KICAgICAgdG9wX2cgPC0gYygpDQogICAgICB0b3BfZyA8LSBjKHRvcF9nLA0KICAgICAgICAgICAgICAgICByZXN1bHRzW2luZCwgJ0dlbmUnXVsNCiAgICAgICAgICAgICAgICAgICBvcmRlcihyZXN1bHRzW2luZCwgJ2ludmVydF9QJ10sIGRlY3JlYXNpbmcgPSBUUlVFKVsxOjE1XV0sDQogICAgICAgICAgICAgICAgIHJlc3VsdHNbaW5kLCAnR2VuZSddW29yZGVyKHJlc3VsdHNbaW5kLCAnaW52ZXJ0X1AnXSwgZGVjcmVhc2luZyA9IEZBTFNFKVsxOjE1XV0pDQogICAgICB0b3BfZzwtIHVuaXF1ZSh0b3BfZykNCiAgICAgIHJlc3VsdHMgPC0gcmVzdWx0c1ssIC0xKm5jb2wocmVzdWx0cyldICMgcmVtb3ZlIGludmVydF9QIGZyb20gbWF0cml4DQogICAgICANCiAgICAgICMgR3JhcGggcmVzdWx0cw0KICAgICAgcGxvdHNbW2NvdW50ZXJdXTwtIGdncGxvdChyZXN1bHRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IEZDLCB5ID0gLWxvZzEwKGFkal9wX3ZhbHVlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gQ29sb3IsIGxhYmVsID0gR2VuZSkpICsNCiAgICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygxLCAtMSksIGx0eSA9ICJkYXNoZWQiKSArDQogICAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IC1sb2cxMCgwLjA1KSwgbHR5ID0gImRhc2hlZCIpICsNCiAgICAgICAgZ2VvbV9wb2ludCgpICsNCiAgICAgICAgbGFicyh4ID0gcGFzdGUoIkVucmljaGVkIGluIiwgYWN0aXZlX2dyb3VwMiwiIDwtIGxvZzIoRkMpIC0+IEVucmljaGVkIGluIiwgYWN0aXZlX2dyb3VwMSksDQogICAgICAgICAgICAgeSA9ICJTaWduaWZpY2FuY2UsIC1sb2cxMChQKSIsDQogICAgICAgICAgICAgY29sb3IgPSAiU2lnbmlmaWNhbmNlIikgKw0KICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhgRkRSIDwgMC4wMDFgID0gImRvZGdlcmJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgRkRSIDwgMC4wNWAgPSAibGlnaHRibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYFAgPCAwLjA1YCA9ICJvcmFuZ2UyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYE5TIG9yIEZDIDwgMC41YCA9ICJncmF5IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBndWlkZSA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSA0KSkpICsNCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLDAuMDUpKSkgKw0KICAgICAgICBnZW9tX3RleHRfcmVwZWwoZGF0YSA9IHN1YnNldChyZXN1bHRzLCBGRFI8MC4wMDEgJiAoLTE+RkN8IEZDPjEpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHBvaW50LnBhZGRpbmcgPSAwLjE1LCBjb2xvciA9ICJibGFjayIsIHNpemU9My41LA0KICAgICAgICAgICAgICAgICAgICAgICAgbWluLnNlZ21lbnQubGVuZ3RoID0gLjEsIGJveC5wYWRkaW5nID0gLjIsIGx3ZCA9IDIsDQogICAgICAgICAgICAgICAgICAgICAgICBtYXgub3ZlcmxhcHMgPSA1MCkgKw0KICAgICAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAyMCkgKw0KICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKw0KICAgICAgICBnZ3RpdGxlKHBhc3RlKCJjbGFzczogIiwgY2xhc3MsIiAtICIsdGVzdCwgbXRjLCJtdWx0aXRlc3QgY29yciIpKQ0KICAgICAgDQogICAgICAjc3RvcmUgdGFibGVzIGZvciBkaXNwbGF5IGxhdGVyDQogICAgICB0YWJsZXNbW2NvdW50ZXJdXTwtcmVzdWx0cw0KICAgICAgDQogICAgICAgY291bnRlciA9IGNvdW50ZXIrMQ0KICAgICAgI2RhdGF0YWJsZShzdWJzZXQocmVzdWx0cywgR2VuZSAlaW4lIEdPSSksIHJvd25hbWVzPUZBTFNFLGNhcHRpb24gPSBwYXN0ZSgiREUgcmVzdWx0cyAiLCBhY3RpdmVfZ3JvdXAxLCIgdnMgIiwgYWN0aXZlX2dyb3VwMikpDQogICAgfQ0KICB9DQp9DQpncmlkLmFycmFuZ2UoZ3JvYnM9cGxvdHMsbmNvbD0yKQ0KYGBgDQoNCmBgYHtyIGR1bXBfdGFibGVzLHJlc3VsdHM9J2FzaXMnfQ0KI3N0cmFuZ2x5IGRvZXMgbm90IGFwcGVhciBpbiBodG1sIG91dHB1dD8/DQpmb3IgKGMgaW4gKDI6Y291bnRlci0xKSkgew0KICAjR2VuZSAlaW4lIEdPSQ0KICANCiAgcHJpbnQoZGF0YXRhYmxlKCBzdWJzZXQodGFibGVzW1tjXV0sIENvbG9yID09ICJGRFIgPCAwLjAwMSIgKSwgDQogICAgICAgICAgIHJvd25hbWVzPUZBTFNFLA0KICAgICAgICAgICBleHRlbnNpb25zID0gJ0J1dHRvbnMnLCBvcHRpb25zID0gbGlzdCAoDQogICAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JykNCiAgICAgICAgICAgICksDQogICAgICAgICAgIGNhcHRpb24gPSBwYXN0ZSgiREUgcmVzdWx0cyAiLCBsYWJlbHNbWzFdXSksZmlsdGVyPSd0b3AnKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJyYXdfcF92YWx1ZSIsImFkal9wX3ZhbHVlIiwiRkRSIiwiRkMiKSwgZGlnaXRzPTMpKQ0KICBjYXQoJ1xuXG48IS0tIC0tPlxuXG4nKQ0KfSAgICAgICAgICAgIA0KDQpgYGANCg0KIyA3LjIgREUgYW5hbHlzaXMgd2l0aCBMTU0NCg0KQSBjb21tb24gc3RhdGlzdGljYWwgYXBwcm9hY2ggaXMgdG8gdXNlIGEgbGluZWFyIG1peGVkLWVmZmVjdCBtb2RlbA0KKExNTSkuIFRoZSBMTU0gYWxsb3dzIHRoZSB1c2VyIHRvIGFjY291bnQgZm9yIHRoZSBzdWJzYW1wbGluZyBwZXINCnRpc3N1ZTsgaW4gb3RoZXIgd29yZHMsIHdlIGFkanVzdCBmb3IgdGhlIGZhY3QgdGhhdCB0aGUgbXVsdGlwbGUgcmVnaW9ucw0Kb2YgaW50ZXJlc3QgcGxhY2VkIHBlciB0aXNzdWUgc2VjdGlvbiBhcmUgbm90IGluZGVwZW5kZW50IG9ic2VydmF0aW9ucywNCmFzIGlzIHRoZSBhc3N1bXB0aW9uIHdpdGggb3RoZXIgdHJhZGl0aW9uYWwgc3RhdGlzdGljYWwgdGVzdHMuIFRoZQ0KZm9ybXVsYXRpb24gb2YgdGhlIExNTSBtb2RlbCBkZXBlbmRzIG9uIHRoZSBzY2llbnRpZmljIHF1ZXN0aW9uIGJlaW5nDQphc2tlZC4NCg0KT3ZlcmFsbCwgdGhlcmUgYXJlIHR3byBmbGF2b3JzIG9mIHRoZSBMTU0gbW9kZWwgd2hlbiB1c2VkIHdpdGggR2VvTXgNCmRhdGE6IGkpIHdpdGggYW5kIGlpKSB3aXRob3V0IHJhbmRvbSBzbG9wZS4NCg0KV2hlbiBjb21wYXJpbmcgZmVhdHVyZXMgdGhhdCBjby1leGlzdCBpbiBhIGdpdmVuIHRpc3N1ZSBzZWN0aW9uLCBhDQpyYW5kb20gc2xvcGUgaXMgaW5jbHVkZWQgaW4gdGhlIExNTSBtb2RlbC4gV2hlbiBjb21wYXJpbmcgZmVhdHVyZXMgdGhhdA0KYXJlIG11dHVhbGx5IGV4Y2x1c2l2ZSBpbiBhIGdpdmVuIHRpc3N1ZSBzZWN0aW9uIHRoZSBMTU0gbW9kZWwgZG9lcyBub3QNCnJlcXVpcmUgYSByYW5kb20gc2xvcGUuDQoNCk1vc3RseSwgd2UgdXNlIHBhdGllbnQvc2FtcGxlIGFzIFJhbmRvbSBJbnRlcmNlcHQgd2hlbiB0aGV5IGFyZSBjb21iaW5lZA0Kb24gc2xpZGVzLg0KDQohW10oQzovVXNlcnMvcGtsb29zdGVybWFuL0RvY3VtZW50cy9nZW5lcmFsX3dvcmtmbG93L0xNTV9zZXR1cC5wbmcpDQoNCiMjIGdsb21lcnVsdXMgLSB0dWJ1bGUNCg0KYGBge3IgTE1NIHJlZ2lvbn0NCiMgY29udmVydCB0ZXN0IHZhcmlhYmxlcyB0byBmYWN0b3JzDQoNCnBEYXRhKHRhcmdldF9EYXRhKSR0ZXN0UmVnaW9uIDwtIA0KICAgIGZhY3RvcihwRGF0YSh0YXJnZXRfRGF0YSkkcmVnaW9uLCBjKCJnbG9tZXJ1bHVzIiwgInR1YnVsZSIpKQ0KDQpwRGF0YSh0YXJnZXRfRGF0YSlbWyJzbGlkZSJdXTwtZmFjdG9yKHBEYXRhKHRhcmdldF9EYXRhKVtbInNsaWRlX25hbWUiXV0pDQphc3NheURhdGFFbGVtZW50KG9iamVjdCA9IHRhcmdldF9EYXRhLCBlbHQgPSAibG9nX3EiKSA8LSAgYXNzYXlEYXRhQXBwbHkodGFyZ2V0X0RhdGEsIDIsIEZVTiA9IGxvZywgYmFzZSA9IDIsIGVsdCA9ICJxX25vcm0iKQ0KDQpsbW1fcmVzdWx0cyA8LSBjKCkNCm1peGVkT3V0bWMgPC0NCiAgICBtaXhlZE1vZGVsREUodGFyZ2V0X0RhdGEsDQogICAgICAgICAgICAgICAgIGVsdCA9ICJsb2dfcSIsDQogICAgICAgICAgICAgICAgIG1vZGVsRm9ybXVsYSA9IH4gdGVzdFJlZ2lvbiArICgxICsgdGVzdFJlZ2lvbiB8IHNsaWRlKSwNCiAgICAgICAgICAgICAgICAgZ3JvdXBWYXIgPSAidGVzdFJlZ2lvbiIsDQogICAgICAgICAgICAgICAgIG5Db3JlcyA9IHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpLA0KICAgICAgICAgICAgICAgICBtdWx0aUNvcmUgPSBGQUxTRSkNCg0KIyBmb3JtYXQgcmVzdWx0cyBhcyBkYXRhLmZyYW1lDQogcl90ZXN0IDwtIGRvLmNhbGwocmJpbmQsIG1peGVkT3V0bWNbImxzbWVhbnMiLCBdKQ0KIHRlc3RzIDwtIHJvd25hbWVzKHJfdGVzdCkNCiByX3Rlc3QgPC0gYXMuZGF0YS5mcmFtZShyX3Rlc3QpDQogcl90ZXN0JENvbnRyYXN0IDwtIHRlc3RzDQogDQojIHVzZSBsYXBwbHkgaW4gY2FzZSB5b3UgaGF2ZSBtdWx0aXBsZSBsZXZlbHMgb2YgeW91ciB0ZXN0IGZhY3RvciB0bw0KIyBjb3JyZWN0bHkgYXNzb2NpYXRlIGdlbmUgbmFtZSB3aXRoIGl0J3Mgcm93IGluIHRoZSByZXN1bHRzIHRhYmxlDQogcl90ZXN0JEdlbmUgPC0gDQogICAgIHVubGlzdChsYXBwbHkoY29sbmFtZXMobWl4ZWRPdXRtYyksDQogICAgICAgICAgICAgICAgICAgcmVwLCBucm93KG1peGVkT3V0bWNbImxzbWVhbnMiLCBdW1sxXV0pKSkNCg0KIHJfdGVzdCRGRFIgPC0gcC5hZGp1c3Qocl90ZXN0JGBQcig+fHR8KWAsIG1ldGhvZCA9ICJmZHIiKQ0KIHJfdGVzdCA8LSByX3Rlc3RbLCBjKCJHZW5lIiwgIkNvbnRyYXN0IiwgIkVzdGltYXRlIiwgIlByKD58dHwpIiwgIkZEUiIpXQ0KIGxtbV9yZXN1bHRzIDwtIHJiaW5kKGxtbV9yZXN1bHRzLCByX3Rlc3QpDQoNCmBgYA0KDQpgYGB7ciB0YWJsZV9vZl9MTU1fcmVzdWx0cyByZWdpb259DQojc3Vic2V0KGxtbV9yZXN1bHRzLCBHZW5lICVpbiUgR09JKQ0KZGF0YXRhYmxlKGxtbV9yZXN1bHRzLCByb3duYW1lcyA9IEZBTFNFLA0KICAgICAgICAgIGV4dGVuc2lvbnMgPSAnQnV0dG9ucycsIG9wdGlvbnMgPSBsaXN0ICgNCiAgICAgICAgICAgICAgZG9tID0gJ0JmdHJpcCcsDQogICAgICAgICAgICAgIGJ1dHRvbnMgPSBjKCdjb3B5JywgJ2NzdicsICdleGNlbCcsICdwZGYnLCAncHJpbnQnKQ0KICAgICAgICAgICAgKSwNCiAgICAgICAgICBjYXB0aW9uID0gIkRFIHJlc3VsdHMgZm9yIEdlbmVzIG9mIEludGVyZXN0ICg+NzUlIENWKSIsZmlsdGVyPSd0b3AnKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJFc3RpbWF0ZSIsIlByKD58dHwpIiwiRkRSIiksIGRpZ2l0cz0zKQ0KYGBgDQoNCiMjIERLRCAtIG5vcm1hbA0KDQpgYGB7ciBMTU0gY2xhc3N9DQojIGNvbnZlcnQgdGVzdCB2YXJpYWJsZXMgdG8gZmFjdG9ycw0KDQpwRGF0YSh0YXJnZXRfRGF0YSkkdGVzdENsYXNzIDwtIA0KICAgIGZhY3RvcihwRGF0YSh0YXJnZXRfRGF0YSkkY2xhc3MsIGMoIkRLRCIsICJub3JtYWwiKSkNCg0KcERhdGEodGFyZ2V0X0RhdGEpW1sic2xpZGUiXV08LWZhY3RvcihwRGF0YSh0YXJnZXRfRGF0YSlbWyJzbGlkZV9uYW1lIl1dKQ0KYXNzYXlEYXRhRWxlbWVudChvYmplY3QgPSB0YXJnZXRfRGF0YSwgZWx0ID0gImxvZ19xIikgPC0gYXNzYXlEYXRhQXBwbHkodGFyZ2V0X0RhdGEsIDIsIEZVTiA9IGxvZywgYmFzZSA9IDIsIGVsdCA9ICJxX25vcm0iKQ0KDQpsbW1fcmVzdWx0c19kIDwtIGMoKQ0KbWl4ZWRPdXRtYyA8LQ0KICAgIG1peGVkTW9kZWxERSh0YXJnZXRfRGF0YSwNCiAgICAgICAgICAgICAgICAgZWx0ID0gImxvZ19xIiwNCiAgICAgICAgICAgICAgICAgbW9kZWxGb3JtdWxhID0gfiB0ZXN0Q2xhc3MgKyAoMSB8IHNsaWRlKSwNCiAgICAgICAgICAgICAgICAgZ3JvdXBWYXIgPSAidGVzdENsYXNzIiwNCiAgICAgICAgICAgICAgICAgbkNvcmVzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCksDQogICAgICAgICAgICAgICAgIG11bHRpQ29yZSA9IEZBTFNFKQ0KDQojIGZvcm1hdCByZXN1bHRzIGFzIGRhdGEuZnJhbWUNCiByX3Rlc3QgPC0gZG8uY2FsbChyYmluZCwgbWl4ZWRPdXRtY1sibHNtZWFucyIsIF0pDQogdGVzdHMgPC0gcm93bmFtZXMocl90ZXN0KQ0KIHJfdGVzdCA8LSBhcy5kYXRhLmZyYW1lKHJfdGVzdCkNCiByX3Rlc3QkQ29udHJhc3QgPC0gdGVzdHMNCiANCiMgdXNlIGxhcHBseSBpbiBjYXNlIHlvdSBoYXZlIG11bHRpcGxlIGxldmVscyBvZiB5b3VyIHRlc3QgZmFjdG9yIHRvDQojIGNvcnJlY3RseSBhc3NvY2lhdGUgZ2VuZSBuYW1lIHdpdGggaXQncyByb3cgaW4gdGhlIHJlc3VsdHMgdGFibGUNCiByX3Rlc3QkR2VuZSA8LSANCiAgICAgdW5saXN0KGxhcHBseShjb2xuYW1lcyhtaXhlZE91dG1jKSwNCiAgICAgICAgICAgICAgICAgICByZXAsIG5yb3cobWl4ZWRPdXRtY1sibHNtZWFucyIsIF1bWzFdXSkpKQ0KDQogcl90ZXN0JEZEUiA8LSBwLmFkanVzdChyX3Rlc3QkYFByKD58dHwpYCwgbWV0aG9kID0gImZkciIpDQogcl90ZXN0IDwtIHJfdGVzdFssIGMoIkdlbmUiLCAiQ29udHJhc3QiLCAiRXN0aW1hdGUiLCAiUHIoPnx0fCkiLCAiRkRSIildDQogbG1tX3Jlc3VsdHNfZCA8LSByYmluZChsbW1fcmVzdWx0c19kLCByX3Rlc3QpDQoNCmBgYA0KDQpgYGB7ciB0YWJsZV9vZl9MTU1fcmVzdWx0cyBjbGFzc30NCiNzdWJzZXQobG1tX3Jlc3VsdHMsIEdlbmUgJWluJSBHT0kpDQpkYXRhdGFibGUobG1tX3Jlc3VsdHNfZCwgcm93bmFtZXMgPSBGQUxTRSwNCiAgICAgICAgICBleHRlbnNpb25zID0gJ0J1dHRvbnMnLCBvcHRpb25zID0gbGlzdCAoDQogICAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JykNCiAgICAgICAgICAgICksDQogICAgICAgICAgY2FwdGlvbiA9ICJERSByZXN1bHRzIGZvciBHZW5lcyBvZiBJbnRlcmVzdCAoPjc1JSBDVikiLGZpbHRlcj0ndG9wJykgJT4lIGZvcm1hdFJvdW5kKGNvbHVtbnM9YygiRXN0aW1hdGUiLCJQcig+fHR8KSIsIkZEUiIpLCBkaWdpdHM9MykNCmBgYA0KDQojIDcuMyBWdWxjYW5vcGxvdCBvZiBMTU0NCg0KIyMgZ2xvbWVydWx1cyAtIHR1YnVsZQ0KDQpgYGB7ciBsbW1fdnVsY2FubyByZWdpb24sIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTEwfQ0KIyBDYXRlZ29yaXplIFJlc3VsdHMgYmFzZWQgb24gUC12YWx1ZSAmIEZEUiBmb3IgcGxvdHRpbmcNCmZjX3RocmVzaG9sZCA9IDAuNQ0KDQpsbW1fcmVzdWx0cyRDb2xvciA8LSBwYXN0ZSgiTlMgb3IgRkMgPCAiLGZjX3RocmVzaG9sZCkNCmxtbV9yZXN1bHRzJENvbG9yW2xtbV9yZXN1bHRzJGBQcig+fHR8KWAgPCAwLjA1XSA8LSAiUCA8IDAuMDUiDQpsbW1fcmVzdWx0cyRDb2xvcltsbW1fcmVzdWx0cyRGRFIgPCAwLjA1XSA8LSAiRkRSIDwgMC4wNSINCmxtbV9yZXN1bHRzJENvbG9yW2xtbV9yZXN1bHRzJEZEUiA8IDAuMDAxXSA8LSAiRkRSIDwgMC4wMDEiDQpsbW1fcmVzdWx0cyRDb2xvclthYnMobG1tX3Jlc3VsdHMkRXN0aW1hdGUpIDwgZmNfdGhyZXNob2xkXSA8LSAiTlMgb3IgRkMgPCAxIg0KbG1tX3Jlc3VsdHMkQ29sb3IgPC0gZmFjdG9yKGxtbV9yZXN1bHRzJENvbG9yLA0KICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiTlMgb3IgRkMgPCAxIiwgIlAgPCAwLjA1IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZEUiA8IDAuMDUiLCAiRkRSIDwgMC4wMDEiKSkNCg0KDQoNCg0KIyBwaWNrIHRvcCBnZW5lcyBmb3IgZWl0aGVyIHNpZGUgb2Ygdm9sY2FubyB0byBsYWJlbA0KIyBvcmRlciBnZW5lcyBmb3IgY29udmVuaWVuY2U6DQpsbW1fcmVzdWx0cyRpbnZlcnRfUCA8LSAoLWxvZzEwKGxtbV9yZXN1bHRzJGBQcig+fHR8KWApKSAqIHNpZ24obG1tX3Jlc3VsdHMkRXN0aW1hdGUpDQp0b3BfZyA8LSBjKCkNCg0KI2xvb3AgaGVyZSBvdmVyIHRlc3RlZCBjb25kaXRpb25zIGlmIGFwcGxpY2FibGUNCiNmb3IobG9jYXRpb24gaW4gYygiQk9UVE9NIiwiVE9QIikpIHsNCnRvcF9nIDwtIGModG9wX2csDQogICAgICAgICAgIGxtbV9yZXN1bHRzWywgJ0dlbmUnXVsNCiAgICAgICAgICAgICBvcmRlcihsbW1fcmVzdWx0c1ssICdpbnZlcnRfUCddLCBkZWNyZWFzaW5nID0gVFJVRSlbMTozMF1dLA0KICAgICAgICAgICBsbW1fcmVzdWx0c1ssICdHZW5lJ11bDQogICAgICAgICAgICAgb3JkZXIobG1tX3Jlc3VsdHNbLCAnaW52ZXJ0X1AnXSwgZGVjcmVhc2luZyA9IEZBTFNFKVsxOjMwXV0pDQoNCnRvcF9nIDwtIHVuaXF1ZSh0b3BfZykNCmxtbV9yZXN1bHRzIDwtIGxtbV9yZXN1bHRzWywgLTEqbmNvbChsbW1fcmVzdWx0cyldICMgcmVtb3ZlIGludmVydF9QIGZyb20gbWF0cml4DQoNCg0KIyBnZXQgc2lnbmlmaWNhbnQgZ2VuZXMgd2l0aCBGRFIgPCAwLjAwMSBhbmQgZm9sZCBjaGFuZ2UgPiAwLjUNCiNsbW1fcmVzdWx0c19zbGljZSA8LSBsbW1fcmVzdWx0c19zbGljZVtsbW1fcmVzdWx0c19zbGljZSRGRFIgPCAwLjAwMSxdDQojbG1tX3Jlc3VsdHNfc2xpY2UgPC0gbG1tX3Jlc3VsdHNfc2xpY2VbbG1tX3Jlc3VsdHNfc2xpY2UkRXN0aW1hdGUgPCAtMC41IHwgbG1tX3Jlc3VsdHNfc2xpY2UkRXN0aW1hdGUgPiAwLjUsXQ0KDQoNCiMgR3JhcGggcmVzdWx0cw0KcHJpbnQoZ2dwbG90KGxtbV9yZXN1bHRzLA0KICAgICAgICAgICAgIGFlcyh4ID0gRXN0aW1hdGUsIHkgPSAtbG9nMTAoYFByKD58dHwpYCksDQogICAgICAgICAgICAgICAgIGNvbG9yID0gQ29sb3IsIGxhYmVsID0gR2VuZSkpICsNCiAgICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYyhmY190aHJlc2hvbGQsIC1mY190aHJlc2hvbGQpLCBsdHkgPSAiZGFzaGVkIikgKw0KICAgICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtbG9nMTAoMC4wNSksIGx0eSA9ICJkYXNoZWQiKSArDQogICAgICAgIGdlb21fcG9pbnQoKSArDQogICAgICAgIGxhYnMoeCA9IHBhc3RlKGxtbV9yZXN1bHRzJENvbnRyYXN0LCAiIGxvZzIoRkMpIiksDQogICAgICAgICAgICAgeSA9ICJTaWduaWZpY2FuY2UsIC1sb2cxMChQKSIsDQogICAgICAgICAgICAgY29sb3IgPSAiU2lnbmlmaWNhbmNlIikgKw0KICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhgRkRSIDwgMC4wMDFgID0gImRvZGdlcmJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgRkRSIDwgMC4wNWAgPSAibGlnaHRibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYFAgPCAwLjA1YCA9ICJvcmFuZ2UyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYE5TIG9yIEZDIDwgMWAgPSAiZ3JheSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gNCkpKSArDQogICAgICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMCwwLjA1KSkpICsNCiAgICAgICAgZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSBzdWJzZXQobG1tX3Jlc3VsdHMsIEZEUiA8IDAuMDAxICYgYWJzKGxtbV9yZXN1bHRzJEVzdGltYXRlKSA+IGZjX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICAgICAgICAgICBwb2ludC5wYWRkaW5nID0gMC4xNSwgY29sb3IgPSAiYmxhY2siLHNpemU9NSwNCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5zZWdtZW50Lmxlbmd0aCA9IC4xLCBib3gucGFkZGluZyA9IC4yLCBsd2QgPSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4Lm92ZXJsYXBzID0gNTApICsNCiAgICAgICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsNCiAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpKQ0KICAgICAgICAjK2ZhY2V0X3dyYXAoflN1YnNldCwgc2NhbGVzID0gImZyZWVfeSIpKQ0KDQpgYGANCg0KIyMgREtEIC0gbm9ybWFsDQoNCmBgYHtyIGxtbV92dWxjYW5vIGNsYXNzLCBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD0xMH0NCiMgQ2F0ZWdvcml6ZSBSZXN1bHRzIGJhc2VkIG9uIFAtdmFsdWUgJiBGRFIgZm9yIHBsb3R0aW5nDQpmY190aHJlc2hvbGQgPSAwLjUNCg0KbG1tX3Jlc3VsdHNfZCRDb2xvciA8LSBwYXN0ZSgiTlMgb3IgRkMgPCAiLGZjX3RocmVzaG9sZCkNCmxtbV9yZXN1bHRzX2QkQ29sb3JbbG1tX3Jlc3VsdHNfZCRgUHIoPnx0fClgIDwgMC4wNV0gPC0gIlAgPCAwLjA1Ig0KbG1tX3Jlc3VsdHNfZCRDb2xvcltsbW1fcmVzdWx0c19kJEZEUiA8IDAuMDVdIDwtICJGRFIgPCAwLjA1Ig0KbG1tX3Jlc3VsdHNfZCRDb2xvcltsbW1fcmVzdWx0c19kJEZEUiA8IDAuMDAxXSA8LSAiRkRSIDwgMC4wMDEiDQpsbW1fcmVzdWx0c19kJENvbG9yW2FicyhsbW1fcmVzdWx0c19kJEVzdGltYXRlKSA8IGZjX3RocmVzaG9sZF0gPC0gIk5TIG9yIEZDIDwgMSINCmxtbV9yZXN1bHRzX2QkQ29sb3IgPC0gZmFjdG9yKGxtbV9yZXN1bHRzX2QkQ29sb3IsDQogICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJOUyBvciBGQyA8IDEiLCAiUCA8IDAuMDUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRkRSIDwgMC4wNSIsICJGRFIgPCAwLjAwMSIpKQ0KDQoNCg0KDQojIHBpY2sgdG9wIGdlbmVzIGZvciBlaXRoZXIgc2lkZSBvZiB2b2xjYW5vIHRvIGxhYmVsDQojIG9yZGVyIGdlbmVzIGZvciBjb252ZW5pZW5jZToNCmxtbV9yZXN1bHRzX2QkaW52ZXJ0X1AgPC0gKC1sb2cxMChsbW1fcmVzdWx0c19kJGBQcig+fHR8KWApKSAqIHNpZ24obG1tX3Jlc3VsdHNfZCRFc3RpbWF0ZSkNCnRvcF9nIDwtIGMoKQ0KDQojbG9vcCBoZXJlIG92ZXIgdGVzdGVkIGNvbmRpdGlvbnMgaWYgYXBwbGljYWJsZQ0KI2Zvcihsb2NhdGlvbiBpbiBjKCJCT1RUT00iLCJUT1AiKSkgew0KdG9wX2cgPC0gYyh0b3BfZywNCiAgICAgICAgICAgbG1tX3Jlc3VsdHNfZFssICdHZW5lJ11bDQogICAgICAgICAgICAgb3JkZXIobG1tX3Jlc3VsdHNfZFssICdpbnZlcnRfUCddLCBkZWNyZWFzaW5nID0gVFJVRSlbMTozMF1dLA0KICAgICAgICAgICBsbW1fcmVzdWx0c19kWywgJ0dlbmUnXVsNCiAgICAgICAgICAgICBvcmRlcihsbW1fcmVzdWx0c19kWywgJ2ludmVydF9QJ10sIGRlY3JlYXNpbmcgPSBGQUxTRSlbMTozMF1dKQ0KDQp0b3BfZyA8LSB1bmlxdWUodG9wX2cpDQpsbW1fcmVzdWx0c19kIDwtIGxtbV9yZXN1bHRzX2RbLCAtMSpuY29sKGxtbV9yZXN1bHRzX2QpXSAjIHJlbW92ZSBpbnZlcnRfUCBmcm9tIG1hdHJpeA0KDQoNCiMgZ2V0IHNpZ25pZmljYW50IGdlbmVzIHdpdGggRkRSIDwgMC4wMDEgYW5kIGZvbGQgY2hhbmdlID4gMC41DQojbG1tX3Jlc3VsdHNfc2xpY2UgPC0gbG1tX3Jlc3VsdHNfc2xpY2VbbG1tX3Jlc3VsdHNfc2xpY2UkRkRSIDwgMC4wMDEsXQ0KI2xtbV9yZXN1bHRzX3NsaWNlIDwtIGxtbV9yZXN1bHRzX3NsaWNlW2xtbV9yZXN1bHRzX3NsaWNlJEVzdGltYXRlIDwgLTAuNSB8IGxtbV9yZXN1bHRzX3NsaWNlJEVzdGltYXRlID4gMC41LF0NCg0KIyBHcmFwaCByZXN1bHRzDQpwcmludChnZ3Bsb3QobG1tX3Jlc3VsdHNfZCwNCiAgICAgICAgICAgICBhZXMoeCA9IEVzdGltYXRlLCB5ID0gLWxvZzEwKGBQcig+fHR8KWApLA0KICAgICAgICAgICAgICAgICBjb2xvciA9IENvbG9yLCBsYWJlbCA9IEdlbmUpKSArDQogICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoZmNfdGhyZXNob2xkLCAtZmNfdGhyZXNob2xkKSwgbHR5ID0gImRhc2hlZCIpICsNCiAgICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gLWxvZzEwKDAuMDUpLCBsdHkgPSAiZGFzaGVkIikgKw0KICAgICAgICBnZW9tX3BvaW50KCkgKw0KICAgICAgICBsYWJzKHggPSBwYXN0ZShsbW1fcmVzdWx0c19kJENvbnRyYXN0LCAiIGxvZzIoRkMpIiksDQogICAgICAgICAgICAgeSA9ICJTaWduaWZpY2FuY2UsIC1sb2cxMChQKSIsDQogICAgICAgICAgICAgY29sb3IgPSAiU2lnbmlmaWNhbmNlIikgKw0KICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhgRkRSIDwgMC4wMDFgID0gImRvZGdlcmJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgRkRSIDwgMC4wNWAgPSAibGlnaHRibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYFAgPCAwLjA1YCA9ICJvcmFuZ2UyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYE5TIG9yIEZDIDwgMWAgPSAiZ3JheSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gNCkpKSArDQogICAgICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMCwwLjA1KSkpICsNCiAgICAgICAgZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSBzdWJzZXQobG1tX3Jlc3VsdHNfZCwgIEZEUiA8IDAuMDAxICYgYWJzKGxtbV9yZXN1bHRzX2QkRXN0aW1hdGUpID4gZmNfdGhyZXNob2xkKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHBvaW50LnBhZGRpbmcgPSAwLjE1LCBjb2xvciA9ICJibGFjayIsc2l6ZT01LA0KICAgICAgICAgICAgICAgICAgICAgICAgbWluLnNlZ21lbnQubGVuZ3RoID0gLjEsIGJveC5wYWRkaW5nID0gLjIsIGx3ZCA9IDIsDQogICAgICAgICAgICAgICAgICAgICAgICBtYXgub3ZlcmxhcHMgPSA1MCkgKw0KICAgICAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKw0KICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikpDQogICAgICAgICMrZmFjZXRfd3JhcCh+U3Vic2V0LCBzY2FsZXMgPSAiZnJlZV95IikpDQoNCmBgYA0KDQojIDcuNCBQbG90dGluZyBHZW5lcyBvZiBJbnRlcmVzdA0KDQpgYGB7ciBwbG90X2dlbmVfb2ZfaW50ZXJlc3QsIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTV9DQpteV9nb2lzIDwtdW5pcXVlKHN1YnNldChsbW1fcmVzdWx0cywgYEZEUmAgPCAwLjAwMSkkR2VuZSkNCnRtcF90Ymw8LXN1YnNldChsbW1fcmVzdWx0cywgR2VuZSAlaW4lIG15X2dvaXMpDQoNCmlmKG5yb3codG1wX3RibCkgPiAxKSB7IA0KICBkYXRhdGFibGUodG1wX3RibCxyb3duYW1lcyA9IEZBTFNFLGNhcHRpb24gPSAiREUgcmVzdWx0cyBmb3IgR2VuZXMgb2YgSW50ZXJlc3QgIixmaWx0ZXI9J3RvcCcpICU+JSBmb3JtYXRSb3VuZChjb2x1bW5zPWMoIkVzdGltYXRlIiwiUHIoPnx0fCkiLCJGRFIiKSwgZGlnaXRzPTMpDQogDQpmb3IgKG15X2dvaSBpbiBteV9nb2lzKSB7DQojIHNob3cgZXhwcmVzc2lvbiBmb3IgYSBzaW5nbGUgdGFyZ2V0DQogIGE8LWdncGxvdChwRGF0YSh0YXJnZXRfRGF0YSksDQogICAgICAgYWVzKHggPSBBTk4xLCBmaWxsID0gQU5OMSwNCiAgICAgICAgICAgeSA9IGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGFbbXlfZ29pLCBdLCBlbHQgPSAicV9ub3JtIikpKSArDQogIGdlb21fdmlvbGluKCkgKw0KICBnZW9tX2ppdHRlcih3aWR0aCA9IC4yKSArDQogIGxhYnMoeSA9IHBhc3RlKG15X2dvaSwiRXhwcmVzc2lvbiIpKSArDQogIHNjYWxlX3lfY29udGludW91cyh0cmFucyA9ICJsb2cyIikgKw0KICBmYWNldF93cmFwKH5BTk4zLCBucm93PTEpICsgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsNCiAgdGhlbWVfYncoKQ0KICBhDQp9DQp9ZWxzZXsNCiAgcHJpbnQoIk5vIHNpZ25pZmljYW50IGxNTSByZXN1bHRzIHRvIHBsb3QiKQ0KfQ0KYGBgDQoNCiMgNy41IEhlYXRtYXAgb2YgU2lnbmlmaWNhbnQgR2VuZXMNCg0KSW4gYWRkaXRpb24gdG8gZ2VuZXJhdGluZyBpbmRpdmlkdWFsIGdlbmUgYm94IHBsb3RzIG9yIHZvbGNhbm8gcGxvdHMsIHdlDQpjYW4gYWdhaW4gY3JlYXRlIGEgaGVhdG1hcCBmcm9tIG91ciBkYXRhLiBUaGlzIHRpbWUgcmF0aGVyIHRoYW4NCnV0aWxpemluZyBDViB0byBzZWxlY3QgZ2VuZXMsIHdlIGNhbiB1c2UgdGhlIFAtdmFsdWUgb3IgRkRSIHZhbHVlcyB0bw0Kc2VsZWN0IGdlbmVzLiBIZXJlLCB3ZSBwbG90IGFsbCBnZW5lcyB3aXRoIGFuIEZEUiBcPCAwLjAwMS4NCg0KYGBge3IgaGVhdG1hcF9zaWdfZ2VuZXMsIGZpZy53aWR0aD0yMCxmaWcuaGVpZ2h0PTIwfQ0KbXlfZ29pcyA8LXVuaXF1ZShzdWJzZXQobG1tX3Jlc3VsdHMsIGBGRFJgIDwgMC4wMDEpJEdlbmUpDQoNCmlmKGxlbmd0aChteV9nb2lzKT09MCkgew0KICBwcmludCgiTm8gc2lnbmlmaWNhbnQgcmVzdWx0cyB0byBzaG93IikNCiANCn1lbHNlew0KDQpwaGVhdG1hcChsb2cyKGFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGFbbXlfZ29pcywgXSwgZWx0ID0gInFfbm9ybSIpKSwNCiAgICAgICAgIHNjYWxlID0gInJvdyIsDQogICAgICAgICBzaG93X3Jvd25hbWVzID0gVFJVRSwgc2hvd19jb2xuYW1lcyA9IFRSVUUsDQogICAgICAgICBib3JkZXJfY29sb3IgPSBOQSwNCiAgICAgICAgIGNsdXN0ZXJpbmdfbWV0aG9kID0gImF2ZXJhZ2UiLA0KICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzID0gImNvcnJlbGF0aW9uIiwNCiAgICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2VfY29scyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgICBjdXRyZWVfY29scyA9IDMsIGN1dHJlZV9yb3dzID0gMiwNCiAgICAgICAgIGJyZWFrcyA9IHNlcSgtMywgMywgMC4wNSksDQogICAgICAgICBjb2xvciA9IGNvbG9yUmFtcFBhbGV0dGUoYygicHVycGxlMyIsICJibGFjayIsICJ5ZWxsb3cyIikpKDEyMCksDQogICAgICAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGNvbG9yX2xpc3QsDQogICAgICAgICBhbm5vdGF0aW9uX2NvbCA9IHBEYXRhKHRhcmdldF9EYXRhKVssIGFubl9uYW1lc10pDQp9DQpgYGANCg0KIyA4IFBhdGh3YXkgQW5hbHlzaXMNCg0KUGF0aHdheSBhbmFseXNpcyBlbmFibGVzIGV4cGxvcmF0aW9uIG9mIGRpZmZlcmVudCBhZ2dyZWdhdGUgZ2VuZSBzZXRzDQpmb3Igb3VyIGV4cGVyaW1lbnRhbCBxdWVzdGlvbnMuIEVhY2ggaW5kaXZpZHVhbCBST0kvQU9JIHNlZ21lbnQgaXMNCnNjb3JlZCBmb3IgZXZlcnkgcGF0aHdheSBvZiBpbnRlcmVzdCwgd2hpY2ggd2UgY2FuIHRoZW4gdXNlIHRvDQppbnZlc3RpZ2F0ZSBiaW9sb2dpY2FsIGRpZmZlcmVuY2VzLiBXZSB3aWxsIHBlcmZvcm0gYW5hbG9nb3VzIGFuYWx5c2VzDQphcyB0aG9zZSBvdXRsaW5lZCBpbiB0aGUgRGlmZmVyZW50aWFsIEV4cHJlc3Npb24gYW5kIFNwYXRpYWwNCkRlY29udm9sdXRpb24gc2VjdGlvbnMgb2YgdGhlIHJlcG9ydCBmb3IgZ2VuZSBzZXQgZW5yaWNobWVudC4NCg0KIyA4LjEgU2NvcmluZyBHZW5lIFNldHMNCg0KUGF0aHdheXMgYW5kIGdlbmUgc2V0cyB3ZXJlIGRlZmluZWQgZnJvbSB0aGUgS2VnZyBCcml0ZSBkYXRhYmFzZS4gV2UgdXNlDQphbiBSIHNvZnR3YXJlIHBhY2thZ2UgY2FsbGVkIEdlbmUgU2V0IFZhcmlhdGlvbiBBbmFseXNpcyB0byBzY29yZSBlYWNoDQpzZWdtZW50IHdpdGhpbiBvdXIgc3R1ZHkuIHNlZQ0KPGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9tc2lnZGJyL3ZpZ25ldHRlcy9tc2lnZGJyLWludHJvLmh0bWw+DQpmb3Igb3B0aW9ucyBvbiBjb2xsZWN0aW9ucy4gV2UgdXNlIHRoZSBLRUdHIGFuZCBSRUFDVE9NRS4NCg0KYGBge3IgYnVpbGRfZ2VuZXNldHN9DQpoX2dlbmVfc2V0cyA9IHJiaW5kKG1zaWdkYnIoc3BlY2llcyA9ICJodW1hbiIsIHN1YmNhdGVnb3J5ID0gIkNQOktFR0ciKSwNCiAgICAgICAgICAgICAgICAgICAgbXNpZ2RicihzcGVjaWVzID0gImh1bWFuIiwgc3ViY2F0ZWdvcnkgPSAiQ1A6UkVBQ1RPTUUiKSkNCiNtc2lnZGJyKHNwZWNpZXMgPSAiaHVtYW4iLCBzdWJjYXRlZ29yeSA9ICJDUDpCSU9DQVJUQSIpDQoNCm1zaWdkYnJfbGlzdCA9IHNwbGl0KHggPSBoX2dlbmVfc2V0cyRnZW5lX3N5bWJvbCwgZiA9IGhfZ2VuZV9zZXRzJGdzX25hbWUpDQoNCiMgcHJlcGFyZSBkZiBmb3IgYWNjdXJhdGUgbWVyZ2luZyBiYWNrIGdlbmVzIGxhdGVyDQpwd19nZW5lX2RmPC1kYXRhLmZyYW1lKFBhdGh3YXkgPSBuYW1lcyhtc2lnZGJyX2xpc3QpKQ0KcHdfZ2VuZV9kZiRnZW5lczwtbXNpZ2Ricl9saXN0DQpgYGANCg0KYGBge3IgcnVuX2dzdmF9DQpzc2dzZWFfcmVzdWx0cyA8LSBHU1ZBOjpnc3ZhKGV4cHIgPSBhc3NheURhdGFFbGVtZW50KHRhcmdldF9EYXRhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsdCA9ICJsb2dfcSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdzZXQuaWR4Lmxpc3QgPSBtc2lnZGJyX2xpc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInpzY29yZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluLnN6ID0gNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXguc3ogPSA1MDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFKQ0KZ2VuZVNldE9iaiA8LQ0KICBOYW5vU3RyaW5nR2VvTXhTZXQoYXNzYXlEYXRhID0gc3Nnc2VhX3Jlc3VsdHMsDQogICAgICAgICAgICAgICAgICAgICBwaGVub0RhdGEgPSBBbm5vdGF0ZWREYXRhRnJhbWUocERhdGEodGFyZ2V0X0RhdGEpKSwNCiAgICAgICAgICAgICAgICAgICAgIHByb3RvY29sRGF0YSA9IHByb3RvY29sRGF0YSh0YXJnZXRfRGF0YSksDQogICAgICAgICAgICAgICAgICAgICBmZWF0dXJlVHlwZSA9ICJHZW5lU2V0IiwNCiAgICAgICAgICAgICAgICAgICAgIGNoZWNrID0gRkFMU0UpDQpgYGANCg0KIyA4LjIgRGlmZmVyZW50YWwgYW5hbHlzaXMgb2YgcGF0aHdheXMNCg0KIyMgZ2xvbWVydWx1cyAtIHR1YnVsZQ0KDQpgYGB7ciBMTU1fb2ZfcGF0aHdheV9hbmFsaXNpcyByZWdpb259DQojICMgY29udmVydCB0ZXN0IHZhcmlhYmxlcyB0byBmYWN0b3JzDQpwRGF0YShnZW5lU2V0T2JqKVtbInNsaWRlIl1dPC1mYWN0b3IocERhdGEoZ2VuZVNldE9iailbWyJzbGlkZV9uYW1lIl1dKQ0KcERhdGEodGFyZ2V0X0RhdGEpJHRlc3RSZWdpb248LWZhY3RvcihwRGF0YSh0YXJnZXRfRGF0YSkkQU5OMywgdW5pcXVlKGNvdW50X21hdCRBTk4zKSkNCg0KbG1tX3NzZ3NlYV9yZXN1bHRzIDwtIGMoKQ0KDQptaXhlZE91dG1jIDwtDQogIG1peGVkTW9kZWxERShnZW5lU2V0T2JqLA0KICAgICAgICAgICAgICAgZWx0ID0gImV4cHJzIiwNCiAgICAgICAgICAgICAgIG1vZGVsRm9ybXVsYSA9IH4gdGVzdFJlZ2lvbiArICgxICsgdGVzdFJlZ2lvbiB8IHNsaWRlKSwNCiAgICAgICAgICAgICAgIGdyb3VwVmFyID0gInRlc3RSZWdpb24iLA0KICAgICAgICAgICAgICAgbkNvcmVzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCksDQogICAgICAgICAgICAgICBtdWx0aUNvcmUgPSBGQUxTRSkNCg0KIyBmb3JtYXQgcmVzdWx0cyBhcyBkYXRhLmZyYW1lDQpyX3NzZ3NlYV90ZXN0IDwtIGRvLmNhbGwocmJpbmQsIG1peGVkT3V0bWNbImxzbWVhbnMiLCBdKQ0Kc3Nnc2VhX3Rlc3RzIDwtIHJvd25hbWVzKHJfc3Nnc2VhX3Rlc3QpDQpyX3NzZ3NlYV90ZXN0IDwtIGFzLmRhdGEuZnJhbWUocl9zc2dzZWFfdGVzdCkNCnJfc3Nnc2VhX3Rlc3QkQ29udHJhc3QgPC0gc3Nnc2VhX3Rlc3RzDQojcl9zc2dzZWFfdGVzdCRHZW5lcyA8LSBtc2lnZGJyX2xpc3Qgc2VlbXMgdW5yZWxpYWJsZSBhcyBnc3ZhIG9taXRzIHBhdGh3YXlzIGlmIGdlbmVzIGFyZSBub3QgaW4gZGF0YS4uDQoNCiMgdXNlIGxhcHBseSBpbiBjYXNlIHlvdSBoYXZlIG11bHRpcGxlIGxldmVscyBvZiB5b3VyIHRlc3QgZmFjdG9yIHRvDQojIGNvcnJlY3RseSBhc3NvY2lhdGUgZ2VuZSBuYW1lIHdpdGggaXQncyByb3cgaW4gdGhlIHJlc3VsdHMgdGFibGUNCnJfc3Nnc2VhX3Rlc3QkUGF0aHdheSA8LQ0KICB1bmxpc3QobGFwcGx5KGNvbG5hbWVzKG1peGVkT3V0bWMpLA0KICAgICAgICAgICAgICAgIHJlcCwgbnJvdyhtaXhlZE91dG1jWyJsc21lYW5zIiwgXVtbMV1dKSkpDQoNCg0Kcl9zc2dzZWFfdGVzdCRGRFIgPC0gcC5hZGp1c3Qocl9zc2dzZWFfdGVzdCRgUHIoPnx0fClgLCBtZXRob2QgPSAiZmRyIikNCnJfc3Nnc2VhX3Rlc3QgPC0gcl9zc2dzZWFfdGVzdFssIGMoIlBhdGh3YXkiLCAiQ29udHJhc3QiLCAiRXN0aW1hdGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUHIoPnx0fCkiLCAiRkRSIildDQpsbW1fc3Nnc2VhX3Jlc3VsdHMgPC0gcmJpbmQobG1tX3NzZ3NlYV9yZXN1bHRzLCByX3NzZ3NlYV90ZXN0KQ0KbG1tX3NzZ3NlYV9yZXN1bHRzIDwtIG1lcmdlKGxtbV9zc2dzZWFfcmVzdWx0cywgcHdfZ2VuZV9kZikNCmBgYA0KDQojIyBES0QgLSBub3JtYWwNCg0KYGBge3IgTE1NX29mX3BhdGh3YXlfYW5hbGlzaXMgY2xhc3N9DQojICMgY29udmVydCB0ZXN0IHZhcmlhYmxlcyB0byBmYWN0b3JzDQpwRGF0YShnZW5lU2V0T2JqKVtbInNsaWRlIl1dPC1mYWN0b3IocERhdGEoZ2VuZVNldE9iailbWyJzbGlkZV9uYW1lIl1dKQ0KcERhdGEodGFyZ2V0X0RhdGEpJHRlc3RDbGFzczwtZmFjdG9yKHBEYXRhKHRhcmdldF9EYXRhKSRBTk4xLCB1bmlxdWUoY291bnRfbWF0JEFOTjEpKQ0KDQpsbW1fc3Nnc2VhX3Jlc3VsdHNfZCA8LSBjKCkNCg0KbWl4ZWRPdXRtYyA8LQ0KICBtaXhlZE1vZGVsREUoZ2VuZVNldE9iaiwNCiAgICAgICAgICAgICAgIGVsdCA9ICJleHBycyIsDQogICAgICAgICAgICAgICBtb2RlbEZvcm11bGEgPSB+IHRlc3RDbGFzcyArICgxIHwgc2xpZGUpLA0KICAgICAgICAgICAgICAgZ3JvdXBWYXIgPSAidGVzdENsYXNzIiwNCiAgICAgICAgICAgICAgIG5Db3JlcyA9IHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpLA0KICAgICAgICAgICAgICAgbXVsdGlDb3JlID0gRkFMU0UpDQoNCiMgZm9ybWF0IHJlc3VsdHMgYXMgZGF0YS5mcmFtZQ0Kcl9zc2dzZWFfdGVzdCA8LSBkby5jYWxsKHJiaW5kLCBtaXhlZE91dG1jWyJsc21lYW5zIiwgXSkNCnNzZ3NlYV90ZXN0cyA8LSByb3duYW1lcyhyX3NzZ3NlYV90ZXN0KQ0Kcl9zc2dzZWFfdGVzdCA8LSBhcy5kYXRhLmZyYW1lKHJfc3Nnc2VhX3Rlc3QpDQpyX3NzZ3NlYV90ZXN0JENvbnRyYXN0IDwtIHNzZ3NlYV90ZXN0cw0KI3Jfc3Nnc2VhX3Rlc3QkR2VuZXMgPC0gbXNpZ2Ricl9saXN0IHNlZW1zIHVucmVsaWFibGUgYXMgZ3N2YSBvbWl0cyBwYXRod2F5cyBpZiBnZW5lcyBhcmUgbm90IGluIGRhdGEuLg0KDQojIHVzZSBsYXBwbHkgaW4gY2FzZSB5b3UgaGF2ZSBtdWx0aXBsZSBsZXZlbHMgb2YgeW91ciB0ZXN0IGZhY3RvciB0bw0KIyBjb3JyZWN0bHkgYXNzb2NpYXRlIGdlbmUgbmFtZSB3aXRoIGl0J3Mgcm93IGluIHRoZSByZXN1bHRzIHRhYmxlDQpyX3NzZ3NlYV90ZXN0JFBhdGh3YXkgPC0NCiAgdW5saXN0KGxhcHBseShjb2xuYW1lcyhtaXhlZE91dG1jKSwNCiAgICAgICAgICAgICAgICByZXAsIG5yb3cobWl4ZWRPdXRtY1sibHNtZWFucyIsIF1bWzFdXSkpKQ0KDQoNCnJfc3Nnc2VhX3Rlc3QkRkRSIDwtIHAuYWRqdXN0KHJfc3Nnc2VhX3Rlc3QkYFByKD58dHwpYCwgbWV0aG9kID0gImZkciIpDQpyX3NzZ3NlYV90ZXN0IDwtIHJfc3Nnc2VhX3Rlc3RbLCBjKCJQYXRod2F5IiwgIkNvbnRyYXN0IiwgIkVzdGltYXRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlByKD58dHwpIiwgIkZEUiIpXQ0KbG1tX3NzZ3NlYV9yZXN1bHRzX2QgPC0gcmJpbmQobG1tX3NzZ3NlYV9yZXN1bHRzX2QsIHJfc3Nnc2VhX3Rlc3QpDQpsbW1fc3Nnc2VhX3Jlc3VsdHNfZCA8LSBtZXJnZShsbW1fc3Nnc2VhX3Jlc3VsdHNfZCwgcHdfZ2VuZV9kZikNCmBgYA0KDQojIDguMi4xIFRhYmxlIG9mIERpZmZlcmVudGFsIGFuYWx5c2lzIG9mIHBhdGh3YXlzDQoNCiMjIGdsb21lcnVsdXMgLSB0dWJ1bGUNCg0KYGBge3IgdGFibGVfb2ZfTE1NX3BhdGh3YXlfcmVzdWx0cyByZWdpb24sIGZpZy53aWR0aD0yMCxmaWcuaGVpZ2h0PTIwfQ0KZGF0YXRhYmxlKHN1YnNldChsbW1fc3Nnc2VhX3Jlc3VsdHMpLCByb3duYW1lcyA9IEZBTFNFLA0KICAgICAgICAgIGV4dGVuc2lvbnMgPSAnQnV0dG9ucycsIG9wdGlvbnMgPSBsaXN0ICgNCiAgICAgICAgICAgICBwYWdlTGVuZ3RoID0gMTAsDQogICAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JykNCiAgICAgICAgICAgICksDQogICAgICAgICAgY2FwdGlvbiA9ICJERSByZXN1bHRzIGZvciBQYXRod2F5cyIsZmlsdGVyPSd0b3AnKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJFc3RpbWF0ZSIsIlByKD58dHwpIiwiRkRSIiksIGRpZ2l0cz01KQ0KYGBgDQoNCiMjIERLRCAtIG5vcm1hbA0KDQpgYGB7ciB0YWJsZV9vZl9MTU1fcGF0aHdheV9yZXN1bHRzIGNsYXNzLCBmaWcud2lkdGg9MjAsZmlnLmhlaWdodD0yMH0NCmRhdGF0YWJsZShzdWJzZXQobG1tX3NzZ3NlYV9yZXN1bHRzX2QpLCByb3duYW1lcyA9IEZBTFNFLA0KICAgICAgICAgIGV4dGVuc2lvbnMgPSAnQnV0dG9ucycsIG9wdGlvbnMgPSBsaXN0ICgNCiAgICAgICAgICAgICBwYWdlTGVuZ3RoID0gMTAsDQogICAgICAgICAgICAgIGRvbSA9ICdCZnRyaXAnLA0KICAgICAgICAgICAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnLCAncGRmJywgJ3ByaW50JykNCiAgICAgICAgICAgICksDQogICAgICAgICAgY2FwdGlvbiA9ICJERSByZXN1bHRzIGZvciBQYXRod2F5cyIsZmlsdGVyPSd0b3AnKSAlPiUgZm9ybWF0Um91bmQoY29sdW1ucz1jKCJFc3RpbWF0ZSIsIlByKD58dHwpIiwiRkRSIiksIGRpZ2l0cz01KQ0KYGBgDQoNCiMgOC4zIFZ1bGNhbm9wbG90IG9mIExNTV9QYXRod2F5cw0KDQpgYGB7ciBsbW1fcHdfdnVsY2FubywgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MTB9DQojIENhdGVnb3JpemUgUmVzdWx0cyBiYXNlZCBvbiBQLXZhbHVlICYgRkRSIGZvciBwbG90dGluZw0KZmNfdGhyZXNob2xkID0gMC41DQoNCmxtbV9zc2dzZWFfcmVzdWx0cyRDb2xvciA8LSAiTlMgb3IgRkMgPCAwLjMiDQpsbW1fc3Nnc2VhX3Jlc3VsdHMkQ29sb3JbbG1tX3NzZ3NlYV9yZXN1bHRzJGBQcig+fHR8KWAgPCAwLjA1XSA8LSAiUCA8IDAuMDUiDQpsbW1fc3Nnc2VhX3Jlc3VsdHMkQ29sb3JbbG1tX3NzZ3NlYV9yZXN1bHRzJEZEUiA8IDAuMDVdIDwtICJGRFIgPCAwLjA1Ig0KbG1tX3NzZ3NlYV9yZXN1bHRzJENvbG9yW2xtbV9zc2dzZWFfcmVzdWx0cyRGRFIgPCAwLjAwMV0gPC0gIkZEUiA8IDAuMDAxIg0KbG1tX3NzZ3NlYV9yZXN1bHRzJENvbG9yW2FicyhsbW1fc3Nnc2VhX3Jlc3VsdHMkRXN0aW1hdGUpIDwgZmNfdGhyZXNob2xkXSA8LSAiTlMgb3IgRkMgPCAwLjMiDQpsbW1fc3Nnc2VhX3Jlc3VsdHMkQ29sb3IgPC0gZmFjdG9yKGxtbV9zc2dzZWFfcmVzdWx0cyRDb2xvciwNCiAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIk5TIG9yIEZDIDwgMC4zIiwgIlAgPCAwLjA1IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZEUiA8IDAuMDUiLCAiRkRSIDwgMC4wMDEiKSkNCg0KIyBwaWNrIHRvcCBwdyBmb3IgZWl0aGVyIHNpZGUgb2Ygdm9sY2FubyB0byBsYWJlbA0KIyBvcmRlciBwdyBmb3IgY29udmVuaWVuY2U6DQpsbW1fc3Nnc2VhX3Jlc3VsdHMkaW52ZXJ0X1AgPC0gKC1sb2cxMChsbW1fc3Nnc2VhX3Jlc3VsdHMkYFByKD58dHwpYCkpICogc2lnbihsbW1fc3Nnc2VhX3Jlc3VsdHMkRXN0aW1hdGUpDQp0b3Bfc3Nnc2VhX2cgPC0gYygpDQoNCiNsb29wIGhlcmUgb3ZlciB0ZXN0ZWQgY29uZGl0aW9ucyBpZiBhcHBsaWNhYmxlDQp0b3Bfc3Nnc2VhX2cgPC0gYyh0b3Bfc3Nnc2VhX2csDQogICAgICAgICAgIGxtbV9zc2dzZWFfcmVzdWx0c1ssICdQYXRod2F5J11bDQogICAgICAgICAgICAgICBvcmRlcihsbW1fc3Nnc2VhX3Jlc3VsdHNbLCAnaW52ZXJ0X1AnXSwgZGVjcmVhc2luZyA9IFRSVUUpWzE6MjBdXSwNCiAgICAgICAgICAgbG1tX3NzZ3NlYV9yZXN1bHRzWywgJ1BhdGh3YXknXVsNCiAgICAgICAgICAgICAgIG9yZGVyKGxtbV9zc2dzZWFfcmVzdWx0c1ssICdpbnZlcnRfUCddLCBkZWNyZWFzaW5nID0gRkFMU0UpWzE6MjBdXSkNCg0KdG9wX3NzZ3NlYV9nIDwtIHVuaXF1ZSh0b3Bfc3Nnc2VhX2cpDQpsbW1fc3Nnc2VhX3Jlc3VsdHMgPC0gbG1tX3NzZ3NlYV9yZXN1bHRzWywgLTEqbmNvbChsbW1fc3Nnc2VhX3Jlc3VsdHMpXSAjIHJlbW92ZSBpbnZlcnRfUCBmcm9tIG1hdHJpeA0KDQojbG1tX3NzZ3NlYV9yZXN1bHRzX3NsaWNlIDwtIGxtbV9zc2dzZWFfcmVzdWx0c19zbGljZVtsbW1fc3Nnc2VhX3Jlc3VsdHNfc2xpY2UkRkRSIDwgMSxdDQoNCiMgR3JhcGggcmVzdWx0cw0KcHJpbnQoZ2dwbG90KGxtbV9zc2dzZWFfcmVzdWx0cywNCiAgICAgICBhZXMoeCA9IEVzdGltYXRlLCB5ID0gLWxvZzEwKGBQcig+fHR8KWApLA0KICAgICAgICAgICBjb2xvciA9IENvbG9yLCBsYWJlbCA9IFBhdGh3YXkpKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygwLjUsIC0wLjUpLCBsdHkgPSAiZGFzaGVkIikgKw0KICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IC1sb2cxMCgwLjA1KSwgbHR5ID0gImRhc2hlZCIpICsNCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIGxhYnMoeCA9IHBhc3RlKGxtbV9yZXN1bHRzJENvbnRyYXN0LCAiIEZDIiksDQogICAgICAgICB5ID0gIlNpZ25pZmljYW5jZSwgLWxvZzEwKFApIiwNCiAgICAgICAgIGNvbG9yID0gIlNpZ25pZmljYW5jZSIpICsNCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhgRkRSIDwgMC4wMDFgID0gImRvZGdlcmJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBGRFIgPCAwLjA1YCA9ICJsaWdodGJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGBQIDwgMC4wNWAgPSAib3JhbmdlMiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYE5TIG9yIEZDIDwgMC41YCA9ICJncmF5IiksDQogICAgICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDQpKSkgKw0KICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMCwwLjA1KSkpICsNCiAgICBnZW9tX3RleHRfcmVwZWwoZGF0YSA9IHN1YnNldChsbW1fc3Nnc2VhX3Jlc3VsdHMsIENvbG9yID09ICJGRFIgPCAwLjA1IiB8IENvbG9yID09ICJGRFIgPCAwLjAwMSIpLA0KICAgICAgICAgICAgICAgICAgIHBvaW50LnBhZGRpbmcgPSAwLjE1LCBjb2xvciA9ICJibGFjayIsc2l6ZT01LA0KICAgICAgICAgICAgICAgICAgIG1pbi5zZWdtZW50Lmxlbmd0aCA9IC4xLCBib3gucGFkZGluZyA9IC4yLCBsd2QgPSAyLA0KICAgICAgICAgICAgICAgICAgIG1heC5vdmVybGFwcyA9IDUwKSArDQogICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikpDQoNCiMgICAgK2ZhY2V0X3dyYXAoflN1YnNldCwgc2NhbGVzID0gImZyZWVfeSIpKQ0KYGBgDQoNCiMgOC40IGhlYXRtYXAgb2YgcGF0aHdheXMNCg0KYGBge3IgcHdfaGVhdG1hcCwgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MjB9DQogIGFjdGl2ZV9wdzwtZmlsdGVyKGxtbV9zc2dzZWFfcmVzdWx0cywgYFByKD58dHwpYCA8IDAuMDAxKSRQYXRod2F5DQogIGFjdGl2ZV9wdzwtZmlsdGVyKGxtbV9zc2dzZWFfcmVzdWx0cywgRkRSIDwgMC4wMDEgKSRQYXRod2F5DQogICNhY3RpdmVfcHc8LWZpbHRlcihsbW1fc3Nnc2VhX3Jlc3VsdHMsIENvbG9yID09ICJGRFIgPCAwLjAwMSIpJFBhdGh3YXkNCiAgDQogIA0KICBhY3RpdmVfcHc8LXRvcF9zc2dzZWFfZw0KICANCiAgaWYgKGxlbmd0aChhY3RpdmVfcHcpPjEpIHsNCiAgDQogICAgcHJpbnQoImdvIikNCiAgICANCiAgcHdfbWF0cml4PC1hc3NheURhdGFFbGVtZW50KGdlbmVTZXRPYmosIGVsdCA9ICJleHBycyIpDQoNCiAgcGhlYXRtYXAocHdfbWF0cml4W2FjdGl2ZV9wdyxdLA0KICAgICAgICAgc2NhbGUgPSAicm93IiwNCiAgICAgICAgIHNob3dfcm93bmFtZXMgPSBUUlVFLCBzaG93X2NvbG5hbWVzID0gVFJVRSwNCiAgICAgICAgIGZvbnRzaXplX3JvdyA9IDEwLA0KICAgICAgICAgYm9yZGVyX2NvbG9yID0gTkEsDQogICAgICAgICBjbHVzdGVyaW5nX21ldGhvZCA9ICJhdmVyYWdlIiwNCiAgICAgICAgICNjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3MgPSAiY29ycmVsYXRpb24iLA0KICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImV1Y2xpZGVhbiIsDQogICAgICAgICBjdXRyZWVfY29scyA9IDIsIGN1dHJlZV9yb3dzID0gMiwNCiAgICAgICAgIGJyZWFrcyA9IHNlcSgtMywgMywgMC4wNSksDQogICAgICAgICAjY29sb3IgPSBjb2xvclJhbXBQYWxldHRlKGMoInB1cnBsZTMiLCAiYmxhY2siLCAieWVsbG93MiIpKSgxMjApLA0KICAgICAgICAgbWFpbiA9ICJIZWF0bWFwIG9mIHNlbGVjdGVkIFBhdGh3YXlzIiwNCiAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gY29sb3JfbGlzdCwNCiAgICAgICAgIGFubm90YXRpb25fY29sID0gcERhdGEodGFyZ2V0X0RhdGEpWywgYW5uX25hbWVzXSkNCiAgDQogIH1lbHNlew0KICAgIHByaW50KCJObyBzaWduaWZpY2FudCByZXN1bHRzIHRvIGRpc3BsYXkiKQ0KICB9DQpgYGANCg0KIyA5IFNwYXRpYWwgRGVjb252b2x1dGlvbg0KDQojIyA5LjEgQ2FsY3VsYXRlIGJhY2tncm91bmRzDQoNCmBgYHtyIHNwYXRpYWxfZGVjb25fYmd9DQpiZyA9IGRlcml2ZV9HZW9NeF9iYWNrZ3JvdW5kKA0KICBub3JtID0gYXNzYXlEYXRhRWxlbWVudCh0YXJnZXRfRGF0YSAsIGVsdCA9ICJxX25vcm0iKSwNCiAgcHJvYmVwb29sID0gZkRhdGEodGFyZ2V0X0RhdGEpJE1vZHVsZSwNCiAgbmVnbmFtZXMgPSBjKCJOZWdQcm9iZS1DVFAwMSIsIk5lZ1Byb2JlLUtpbG8iLCJOZWdhdGl2ZSBQcm9iZSIsICJOZWdQcm9iZS1XVFgiICkpDQogICNuZWduYW1lcyA9ICJOZWdQcm9iZS1XVFgiKQ0KDQpgYGANCg0KIyMgOS4yIExvYWQgY2VsbCBwcm9maWxlDQoNCkEgImNlbGwgcHJvZmlsZSBtYXRyaXgiIGlzIGEgcHJlLWRlZmluZWQgbWF0cml4IHRoYXQgc3BlY2lmaWVzIHRoZQ0KZXhwZWN0ZWQgZXhwcmVzc2lvbiBwcm9maWxlcyBvZiBlYWNoIGNlbGwgdHlwZSBpbiB0aGUgZXhwZXJpbWVudC4gVGhlDQpTcGF0aWFsRGVjb24gbGlicmFyeSBjb21lcyB3aXRoIG9uZSBzdWNoIG1hdHJpeCBwcmUtbG9hZGVkLCB0aGUNCiJTYWZlVE1FIiBtYXRyaXgsIGRlc2lnbmVkIGZvciBlc3RpbWF0aW9uIG9mIGltbXVuZSBhbmQgc3Ryb21hIGNlbGxzIGluDQp0aGUgdHVtb3IgbWljcm9lbnZpcm9ubWVudC4gKFRoaXMgbWF0cml4IHdhcyBkZXNpZ25lZCB0byBhdm9pZCBnZW5lcw0KY29tbW9ubHkgZXhwcmVzc2VkIGJ5IGNhbmNlciBjZWxsczsgc2VlIHRoZSBTcGF0aWFsRGVjb24gbWFudXNjcmlwdCBmb3INCmRldGFpbHMuKS4gT3RoZXJ3aXNlLCBsb2FkIHNwZWNpZmljIHByb2ZpbGVzIGZyb20NCjxodHRwczovL2dpdGh1Yi5jb20vTmFub3N0cmluZy1CaW9zdGF0cy9DZWxsUHJvZmlsZUxpYnJhcnkvdHJlZS9OZXdQcm9maWxlTWF0cmljZXM+DQoNCmBgYHtyIGxvYWRfY2VsbF9wcm9maWxlc30NCiNzYWZlVE1FDQpkYXRhKCJzYWZlVE1FIikNCmRhdGEoInNhZmVUTUUubWF0Y2hlcyIpDQpjdXJyZW50X2NlbGxfcHJvZmlsZTwtc2FmZVRNRQ0KDQojc2VlOiBodHRwczovL2dpdGh1Yi5jb20vTmFub3N0cmluZy1CaW9zdGF0cy9DZWxsUHJvZmlsZUxpYnJhcnkvdHJlZS9OZXdQcm9maWxlTWF0cmljZXMNCg0KIyBjdXJyZW50X2NlbGxfcHJvZmlsZSA8LSBkb3dubG9hZF9wcm9maWxlX21hdHJpeChzcGVjaWVzID0gIkh1bWFuIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWdlX2dyb3VwID0gIkFkdWx0IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF0cml4bmFtZSA9ICJCbGFkZGVyX01DQSIpDQoNCmhlYXRtYXAoc3dlZXAoY3VycmVudF9jZWxsX3Byb2ZpbGUsIDEsIGFwcGx5KGN1cnJlbnRfY2VsbF9wcm9maWxlLCAxLCBtYXgpLCAiLyIpLA0KICAgICAgICBsYWJSb3cgPSBOQSwgbWFyZ2lucyA9IGMoMTAsIDUpLCBjZXhDb2wgPSAwLjcpDQpgYGANCg0KIyA5LjMgUnVuIHNwYXRpYWwgZGVjb252b2x1dGlvbg0KDQpgYGB7ciBzcGF0aWFsX2RlY29uX3J1bn0NCiMgdmVjdG9yIGlkZW50aWZ5aW5nIHB1cmUgdHVtb3Igc2VnbWVudHM6DQojdGFyZ2V0X0RhdGEkaXN0dW1vciA9IHRhcmdldF9EYXRhJEFOTjMgPT0gIkNPUkUiICYgdGFyZ2V0X0RhdGEkQU5OMSA9PSAiUGFuQ0srIg0KDQpyZXMgPSBydW5zcGF0aWFsZGVjb24ob2JqZWN0ID0gdGFyZ2V0X0RhdGEsDQogICAgICAgICAgICAgICAgICAgICAgbm9ybV9lbHQgPSAicV9ub3JtIiwNCiAgICAgICAgICAgICAgICAgICAgICByYXdfZWx0ID0gImV4cHJzIiwNCiAgICAgICAgICAgICAgICAgICAgICAjaXNfcHVyZV90dW1vciA9IHRhcmdldF9EYXRhJGlzdHVtb3IsDQogICAgICAgICAgICAgICAgICAgICAgY2VsbF9jb3VudHMgPSB0YXJnZXRfRGF0YSRudWNsZWksDQogICAgICAgICAgICAgICAgICAgICAgWCA9IGN1cnJlbnRfY2VsbF9wcm9maWxlLA0KICAgICAgICAgICAgICAgICAgICAgIGNlbGxtZXJnZXMgPSBzYWZlVE1FLm1hdGNoZXMsICAgICAgICAgICAgICAjIHNhZmVUTUUubWF0Y2hlcyBvYmplY3QsIHVzZWQgYnkgZGVmYXVsdA0KICAgICAgICAgICAgICAgICAgICAgICNuX3R1bW9yX2NsdXN0ZXJzID0gNSwgICAgICAgICAgICAgICAgICAgICAgIyBob3cgbWFueSBkaXN0aW5jdCB0dW1vciBwcm9maWxlcyB0byBhcHBlbmQgdG8gc2FmZVRNRQ0KICAgICAgICAgICAgICAgICAgICAgIGFsaWduX2dlbmVzID0gVFJVRSkNCg0KDQpgYGANCg0KIyA5LjMuMSBTcGF0aWFsIGRlY29udm9sdXRpb24gaGVhdG1hcHMgey50YWJzZXQgLnRhYnNldC1waWxsc30NCg0KIyMgQWJ1bmRhbmNlDQoNCmBgYHtyIHNwYXRpYWxfZGVjb25faGVhdG1hcCwgZmlnLndpZHRoPTI1LGZpZy5oZWlnaHQ9MTV9DQojIE5PVEU6IGNoZWNrIGNsdXN0ZXJpbmcuLiB3aHkgZGlmZmVyZW50Pw0KDQojc2V0IGRpc3BsYXkgdGhyZXNob2xkcw0KdGhyZXNoIDwtIHNpZ25pZihxdWFudGlsZShyZXMkYmV0YSwgMC45NyksIDIpDQoNCiMgcGxvdCBzdG9yZWQgdG8ga2VlcCBjbHVzdGVyaW5nIGZvciBsYXRlcg0KcDE8LXBoZWF0bWFwKHBtaW4odChyZXMkYmV0YSksdGhyZXNoKSwNCiAgICAgICAgICNzY2FsZSA9ICJyb3ciLCANCiAgICAgICAgIGN1dHJlZV9jb2xzID0gMywNCiAgICAgICAgIGN1dHJlZV9yb3dzID0gMiwNCiAgICAgICAgIGZvbnRzaXplX3JvdyA9IDEyLA0KICAgICAgICAgc2hvd19yb3duYW1lcyA9IFRSVUUsIHNob3dfY29sbmFtZXMgPSBUUlVFLA0KICAgICAgICAgYW5nbGVfY29sID0gIjkwIiwNCiAgICAgICAgIGJvcmRlcl9jb2xvciA9IE5BLA0KICAgICAgICAgI2NsdXN0ZXJpbmdfbWV0aG9kID0gImF2ZXJhZ2UiLA0KICAgICAgICAgI2NsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgICAjY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImNvcnJlbGF0aW9uIiwNCiAgICAgICAgIGxlZ2VuZF9icmVha3MgPSBjKHJvdW5kKHNlcSgwLCB0aHJlc2gsIGxlbmd0aC5vdXQgPSA1KSlbLTVdLCB0aHJlc2gpLA0KICAgICAgICAgbGVnZW5kX2xhYmVscyA9IGMocm91bmQoc2VxKDAsIHRocmVzaCwgbGVuZ3RoLm91dCA9IDUpKVstNV0sIHBhc3RlMCgiQWJ1bmRhbmNlIHNjb3JlcyxcbnRydW5jYXRlZCBhYm92ZSBhdCAiLCB0aHJlc2gpKSwNCiAgICAgICAgICNicmVha3MgPSBzZXEoMCwgNSwgMSksDQogICAgICAgICBjb2xvciA9IGNvbG9yUmFtcFBhbGV0dGUoYygid2hpdGUiLCJkYXJrYmx1ZSIpKSgxMDApLA0KICAgICAgICAgYW5ub3RhdGlvbl9jb2xvcnMgPSBjb2xvcl9saXN0LA0KICAgICAgICAgYW5ub3RhdGlvbl9jb2wgPSBwRGF0YSh0YXJnZXRfRGF0YSlbLCBhbm5fbmFtZXNdDQogICAgICAgICApDQojcDENCmBgYA0KDQojIyBQcm9wb3J0aW9uYWwNCg0KYGBge3Igc3BhdGlhbF9kZWNvbl9wcm9waGVhdG1hcCwgZmlnLndpZHRoPTI1LGZpZy5oZWlnaHQ9MTV9DQojIHByb3BvcnRpb25zOg0KcHJvcHMgPC0gcmVwbGFjZShyZXMkcHJvcF9vZl9ub250dW1vciwgaXMubmEocmVzJHByb3Bfb2Zfbm9udHVtb3IpLCAwKQ0KDQpwMjwtcGhlYXRtYXAodChwcm9wcyksDQogICAgICAgICAjc2NhbGUgPSAicm93IiwgDQogICAgICAgICBjdXRyZWVfY29scyA9IDMsDQogICAgICAgICBjdXRyZWVfcm93cyA9IDIsDQogICAgICAgICBmb250c2l6ZV9yb3cgPSAxMiwNCiAgICAgICAgIHNob3dfcm93bmFtZXMgPSBUUlVFLCBzaG93X2NvbG5hbWVzID0gVFJVRSwNCiAgICAgICAgIGFuZ2xlX2NvbCA9ICI5MCIsDQogICAgICAgICBib3JkZXJfY29sb3IgPSBOQSwNCiAgICAgICAgICNjbHVzdGVyaW5nX21ldGhvZCA9ICJhdmVyYWdlIiwNCiAgICAgICAgICNjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3MgPSAiY29ycmVsYXRpb24iLA0KICAgICAgICAgI2NsdXN0ZXJpbmdfZGlzdGFuY2VfY29scyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgICBsZWdlbmRfYnJlYWtzID0gcm91bmQoc2VxKDAsIG1heChwcm9wcykgKiAwLjk5LCBsZW5ndGgub3V0ID0gNSksIDIpLA0KICAgICAgICAgbGVnZW5kX2xhYmVscyA9IGMocm91bmQoc2VxKDAsIG1heChwcm9wcyksIGxlbmd0aC5vdXQgPSA1KSwgMilbLTVdLCAiUHJvcG9ydGlvbiBvZiBhbGxcbmZpdHRlZCBwb3B1bGF0aW9ucyIpLA0KICAgICAgICAgY29sb3IgPSBjb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwiZGFya2JsdWUiKSkoMTAwKSwNCiAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gY29sb3JfbGlzdCwNCiAgICAgICAgIGFubm90YXRpb25fY29sID0gcERhdGEodGFyZ2V0X0RhdGEpWywgYW5uX25hbWVzXSkNCg0KI3AyDQoNCmBgYA0KDQojIyBTY2FsZWQNCg0KYGBge3Igc3BhdGlhbF9kZWNvbl9zY2FsZWRoZWF0bWFwLCBmaWcud2lkdGg9MjUsZmlnLmhlaWdodD0xNX0NCiMgc2NhbGVkIGFidW5kYW5jZXM6DQplcHNpbG9uIDwtIG1pbihyZXMkYmV0YVtyZXMkYmV0YSA+IDBdKQ0KbWF0IDwtIHN3ZWVwKHJlcyRiZXRhLCAxLCBwbWF4KGFwcGx5KHJlcyRiZXRhLCAxLCBtYXgpLCBlcHNpbG9uKSwgIi8iKQ0KDQpwaGVhdG1hcCh0KG1hdCksDQogICAgICAgICAjc2NhbGUgPSAicm93IiwNCiAgICAgICAgIGN1dHJlZV9jb2xzID0gMywNCiAgICAgICAgIGN1dHJlZV9yb3dzID0gMywNCiAgICAgICAgIGZvbnRzaXplX3JvdyA9IDEyLA0KICAgICAgICAgc2hvd19yb3duYW1lcyA9IFRSVUUsIHNob3dfY29sbmFtZXMgPSBUUlVFLA0KICAgICAgICAgYW5nbGVfY29sID0gIjkwIiwNCiAgICAgICAgIGJvcmRlcl9jb2xvciA9IE5BLA0KICAgICAgICAgI2NsdXN0ZXJpbmdfbWV0aG9kID0gImF2ZXJhZ2UiLA0KICAgICAgICAgI2NsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9ICJjb3JyZWxhdGlvbiIsDQogICAgICAgICAjY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gImNvcnJlbGF0aW9uIiwNCiAgICAgICAgIGxlZ2VuZF9icmVha3MgPSBjKHJvdW5kKHNlcSgwLCAxLCBsZW5ndGgub3V0ID0gNSksIDIpWy01XSwgMSksDQogICAgICAgICBsZWdlbmRfbGFiZWxzID0gYyhyb3VuZChzZXEoMCwgMSwgbGVuZ3RoLm91dCA9IDUpLCAyKVstNV0sICJTY2FsZWQgYWJ1bmRhbmNlXG4ocmF0aW8gdG8gbWF4KSIpLA0KICAgICAgICAgY29sb3IgPSBjb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwiZGFya2JsdWUiKSkoMTAwKSwNCiAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gY29sb3JfbGlzdCwNCiAgICAgICAgIGFubm90YXRpb25fY29sID0gcERhdGEodGFyZ2V0X0RhdGEpWywgYW5uX25hbWVzXSkNCg0KYGBgDQoNCiMgOS40IEJhcnBsb3RzIHsudGFic2V0IC50YWJzZXQtcGlsbHN9DQoNCiMjIGFidW5kYW5jZQ0KDQpgYGB7ciBTRF9hYnVuZGFuY2VfYmFycGxvdCwgZmlnLndpZHRoPTI1LGZpZy5oZWlnaHQ9MTV9DQojIGRlZmluZSB2YXJpYWJsZXMgdG8gc2hvdyBpbiBoZWF0bWFwczoNCnZhcmlhYmxlc190b19wbG90IDwtIGMoInNsaWRlX25hbWUiLCAicmVnaW9uIiwgImNsYXNzIikNCg0KY29sIDwtIGNlbGxjb2xzDQoNCmxheW91dChtYXRyaXgoYygxLCAyLCAzLCAzKSwgbnJvdyA9IDIpLA0KICAgICAgIHdpZHRocyA9IGMoMTAsIDMsIDEwLCAzKSwNCiAgICAgICBoZWlnaHRzID0gYygxLCA4LCAxMCksDQogICAgICApDQoNCnBhcihtYXIgPSBjKDAsIDguMiwgMCwgMC4yKSkNCnBsb3QocDEkdHJlZV9jb2wsIGxhYmVscyA9IEYsIG1haW4gPSAiIiwgeWxhYiA9ICIiLCB5YXh0ID0gIm4iKQ0KcGFyKG1hciA9IGMoMTUsIDgsIDAsIDApKQ0KDQojIGRhdGEgdG8gcGxvdDoNCm1hdCA8LSB0KHJlcyRiZXRhKVssIHAxJHRyZWVfY29sJG9yZGVyXQ0KIyBpbmZlciBzY2FsZSBvZiBuZWdhdGl2ZSB5LWF4aXMgZm9yIGFubm90YXRpb24gY29sb3JiYXJzDQp5bWluIDwtIC1tYXgoY29sU3VtcyhtYXQpKSAqIDAuMTUNCmlmICghaXMuZmluaXRlKHltaW4pKSB7DQogIHltaW4gPC0gMA0KfQ0KDQojIGRyYXcgYmFycGxvdDoNCmJwIDwtIGJhcnBsb3QobWF0LA0KICAgICAgICAgICAgICBjZXgubGFiID0gMS41LA0KICAgICAgICAgICAgICBjb2wgPSBjb2wsIGJvcmRlciA9IE5BLA0KICAgICAgICAgICAgICBjZXgubmFtZXMgPSAxLjEsDQogICAgICAgICAgICAgIGxhcyA9IDIsIG1haW4gPSAiIiwgeWxhYiA9ICJBYnVuZGFuY2Ugc2NvcmVzIiwNCiAgICAgICAgICAgICAgeWxpbSA9IGMoeW1pbiwgbWF4KGNvbFN1bXMobWF0KSkpDQopDQoNCg0KIyBhZGQgY29sb3IgYmFycyBmb3IgYW5ub3RhdGlvbnMNCmZvciAobmFtZSBpbiByZXYodmFyaWFibGVzX3RvX3Bsb3QpKSB7DQogIHlyYW5nZSA8LSBzZXEoeW1pbiAvIDMsIHltaW4sIGxlbmd0aC5vdXQgPSBsZW5ndGgodmFyaWFibGVzX3RvX3Bsb3QpICsgMSlbbWF0Y2gobmFtZSwgdmFyaWFibGVzX3RvX3Bsb3QpICsgYygwLCAxKV0NCiAgeHdpZHRoIDwtIChicFsyXSAtIGJwWzFdKSAvIDINCiAgZm9yIChpIGluIDE6bmNvbChtYXQpKSB7DQogICAgcmVjdChicFtpXSAtIHh3aWR0aCwgeXJhbmdlWzJdLCBicFtpXSArIHh3aWR0aCwgeXJhbmdlWzFdLA0KICAgICAgICAgIyBib3JkZXIgPSBOQSwgY29sID0gYW5uX2NvbG9yc1tbbmFtZV1dW3NlZ21lbnRBbm5vdGF0aW9uc1ttYXRjaChjb2xuYW1lcyhtYXQpW2ldLCBzZWdtZW50QW5ub3RhdGlvbnMkc2VnbWVudElEKSwgbmFtZV1dDQogICAgICAgICAjYm9yZGVyID0gTkEsIGNvbCA9IGFubl9jb2xvcnNbW25hbWVdXVthbm5bcDEkdHJlZV9jb2wkb3JkZXJbaV0sIG5hbWVdXQ0KICAgICAgICAgYm9yZGVyID0gTkEsIGNvbCA9IGNvbG9yX2xpc3RbW25hbWVdXVthbm5bY29sbmFtZXMobWF0KVtpXSwgbmFtZV1dDQogICAgKQ0KICB9DQp9DQpheGlzKDIsDQogICAgIGF0ID0gc2VxKHltaW4gLyAzLCB5bWluLCBsZW5ndGgub3V0ID0gbGVuZ3RoKHZhcmlhYmxlc190b19wbG90KSArIDIpWy1jKDEsIGxlbmd0aCh2YXJpYWJsZXNfdG9fcGxvdCkgKyAyKV0sDQogICAgIGxhcyA9IDIsIGxhYmVscyA9IHZhcmlhYmxlc190b19wbG90LCBsdHkgPSAwLCBjZXguYXhpcyA9IDEuMg0KKQ0KDQojZHJhdyBhIGxlZ2VuZDoNCnBhcihtYXIgPSBjKDAuMSwgMC4xLCAwLjEsIDAuMSkpDQpmcmFtZSgpDQpsZWdlbmRjb2xzIDwtIGxlZ2VuZG5hbWVzIDwtIGMoKQ0KI2ZvciAobmFtZSBpbiByZXYobmFtZXMoYW5uX2NvbG9ycykpKSB7DQpmb3IgKG5hbWUgaW4gYygic2xpZGVfbmFtZSIsICJyZWdpb24iLCJjbGFzcyIpKSB7DQogIGxlZ2VuZGNvbHMgPC0gYyhsZWdlbmRjb2xzLCBOQSwgY29sb3JfbGlzdFtbbmFtZV1dLCBOQSkNCiAgbGVnZW5kbmFtZXMgPC0gYyhsZWdlbmRuYW1lcywgbmFtZSwgbmFtZXMoY29sb3JfbGlzdFtbbmFtZV1dKSwgTkEpDQp9DQpsZWdlbmQoImNlbnRlciIsDQogICAgICAgcGNoID0gMTUsDQogICAgICAgY2V4ID0gMS41LA0KICAgICAgIGNvbCA9IGMobGVnZW5kY29scywgcmVwKE5BLCAxKSwgcmV2KGNvbCkpLA0KICAgICAgIGxlZ2VuZCA9IGMobGVnZW5kbmFtZXMsICJDZWxsIHR5cGUiLCByZXYobmFtZXMoY29sKSkpLA0KKQ0KYGBgDQoNCiMjIHByb3BvcnRpb25hbA0KDQpgYGB7ciBTRF9wcm9wX2JhcnBsb3QsIGZpZy53aWR0aD0yNSxmaWcuaGVpZ2h0PTE1fQ0KIyBkZWZpbmUgdmFyaWFibGVzIHRvIHNob3cgaW4gaGVhdG1hcHM6DQp2YXJpYWJsZXNfdG9fcGxvdCA8LSBjKCJzbGlkZSBuYW1lIiwgInNlZ21lbnQiLCJwaGVubyIpDQoNCmxheW91dChtYXRyaXgoYygxLCAyLCAzLCAzKSwgbnJvdyA9IDIpLA0KICAgICAgIHdpZHRocyA9IGMoMTAsIDMsIDEwLCAzKSwNCiAgICAgICBoZWlnaHRzID0gYygxLCA4LCAxMCksDQogICAgICApDQoNCnBhcihtYXIgPSBjKDAsIDguMiwgMCwgMC4yKSkNCnBsb3QocDIkdHJlZV9jb2wsIGxhYmVscyA9IEYsIG1haW4gPSAiIiwgeWxhYiA9ICIiLCB5YXh0ID0gIm4iKQ0KcGFyKG1hciA9IGMoMTUsIDgsIDAsIDApKQ0KDQojIGRhdGEgdG8gcGxvdDoNCm1hdCA8LSB0KHJlcyRwcm9wX29mX25vbnR1bW9yKVssIHAyJHRyZWVfY29sJG9yZGVyXQ0KICBtYXQgPC0gcmVwbGFjZShtYXQsIGlzLm5hKG1hdCksIDApDQogICMgaW5mZXIgc2NhbGUgb2YgbmVnYXRpdmUgeS1heGlzIGZvciBhbm5vdGF0aW9uIGNvbG9yYmFycw0KICB5bWluIDwtIC0wLjE1DQoNCiMgZHJhdyBiYXJwbG90Og0KYnAgPC0gYmFycGxvdChtYXQsDQogICAgICAgICAgICAgIGNleC5sYWIgPSAxLjUsDQogICAgICAgICAgICAgIGNvbCA9IGNvbCwgYm9yZGVyID0gTkEsDQogICAgICAgICAgICAgIGNleC5uYW1lcyA9IDEuMSwNCiAgICAgICAgICAgICAgbGFzID0gMiwgbWFpbiA9ICIiLCB5bGFiID0gIlByb3BvcnRpb24gb2YgZml0dGVkIGNlbGxzIiwNCiAgICAgICAgICAgICAgeWxpbSA9IGMoeW1pbiwgbWF4KGNvbFN1bXMobWF0KSkpDQopDQoNCiMgYWRkIGNvbG9yIGJhcnMgZm9yIGFubm90YXRpb25zDQpmb3IgKG5hbWUgaW4gcmV2KHZhcmlhYmxlc190b19wbG90KSkgew0KICB5cmFuZ2UgPC0gc2VxKHltaW4gLyAzLCB5bWluLCBsZW5ndGgub3V0ID0gbGVuZ3RoKHZhcmlhYmxlc190b19wbG90KSArIDEpW21hdGNoKG5hbWUsIHZhcmlhYmxlc190b19wbG90KSArIGMoMCwgMSldDQogIHh3aWR0aCA8LSAoYnBbMl0gLSBicFsxXSkgLyAyDQogIGZvciAoaSBpbiAxOm5jb2wobWF0KSkgew0KICAgIHJlY3QoYnBbaV0gLSB4d2lkdGgsIHlyYW5nZVsyXSwgYnBbaV0gKyB4d2lkdGgsIHlyYW5nZVsxXSwNCiAgICAgICAgICMgYm9yZGVyID0gTkEsIGNvbCA9IGFubl9jb2xvcnNbW25hbWVdXVtzZWdtZW50QW5ub3RhdGlvbnNbbWF0Y2goY29sbmFtZXMobWF0KVtpXSwgc2VnbWVudEFubm90YXRpb25zJHNlZ21lbnRJRCksIG5hbWVdXQ0KICAgICAgICAgI2JvcmRlciA9IE5BLCBjb2wgPSBhbm5fY29sb3JzW1tuYW1lXV1bYW5uW3AyJHRyZWVfY29sJG9yZGVyW2ldLCBuYW1lXV0NCiAgICAgICAgIGJvcmRlciA9IE5BLCBjb2wgPSBjb2xvcl9saXN0W1tuYW1lXV1bYW5uW2NvbG5hbWVzKG1hdClbaV0sIG5hbWVdXQ0KICAgICkNCiAgfQ0KfQ0KYXhpcygyLA0KICAgICBhdCA9IHNlcSh5bWluIC8gMywgeW1pbiwgbGVuZ3RoLm91dCA9IGxlbmd0aCh2YXJpYWJsZXNfdG9fcGxvdCkgKyAyKVstYygxLCBsZW5ndGgodmFyaWFibGVzX3RvX3Bsb3QpICsgMildLA0KICAgICBsYXMgPSAyLCBsYWJlbHMgPSB2YXJpYWJsZXNfdG9fcGxvdCwgbHR5ID0gMCwgY2V4LmF4aXMgPSAxLjINCikNCg0KDQojZHJhdyBhIGxlZ2VuZDoNCnBhcihtYXIgPSBjKDAuMSwgMC4xLCAwLjEsIDAuMSkpDQpmcmFtZSgpDQpsZWdlbmRjb2xzIDwtIGxlZ2VuZG5hbWVzIDwtIGMoKQ0KI2ZvciAobmFtZSBpbiByZXYobmFtZXMoYW5uX2NvbG9ycykpKSB7DQpmb3IgKG5hbWUgaW4gYygic2xpZGUgbmFtZSIsICJzZWdtZW50IiwicGhlbm8iKSkgew0KICBsZWdlbmRjb2xzIDwtIGMobGVnZW5kY29scywgTkEsIGNvbG9yX2xpc3RbW25hbWVdXSwgTkEpDQogIGxlZ2VuZG5hbWVzIDwtIGMobGVnZW5kbmFtZXMsIG5hbWUsIG5hbWVzKGNvbG9yX2xpc3RbW25hbWVdXSksIE5BKQ0KfQ0KbGVnZW5kKCJjZW50ZXIiLA0KICAgICAgIHBjaCA9IDE1LA0KICAgICAgIGNleCA9IDEuNCwNCiAgICAgICBjb2wgPSBjKGxlZ2VuZGNvbHMsIHJlcChOQSwgMSksIHJldihjb2wpKSwNCiAgICAgICBsZWdlbmQgPSBjKGxlZ2VuZG5hbWVzLCAiQ2VsbCB0eXBlIiwgcmV2KG5hbWVzKGNvbCkpKSwNCikNCmBgYA0KDQojIDEwIENvZGUgJiBWZXJzaW9ucw0KDQpQaXBlbGluZXZlcnNpb246IHYxIGJhc2VkIG9uOg0KPGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9kZXZlbC93b3JrZmxvd3MvdmlnbmV0dGVzL0dlb014V29ya2Zsb3dzL2luc3QvZG9jL0dlb214VG9vbHNfUk5BLU5HU19BbmFseXNpcy5odG1sPg0KDQpUaGUgdW5kZXJseWluZyBjb2RlIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gdGhlICdDb2RlJywgYnV0dG9uIG9uIHRoZSB0b3ANCm9mIHRoaXMgcGFnZS4gQ2hvb3NlIG9wdGlvbiAnZG93bmxvYWQgUm1kJyB0byBkb3dubG9hZCB0aGUgZnVsbCBwaXBlbGluZQ0Kd2hpY2ggY2FuIGJlIG9wZW5lZCBpbiBSIG9yIFJzdHVkaW8uIFNvbWUgZmlsZXBhdGhzIGFyZSBoYXJkY29kZWQgYW5kDQpuZWVkIHRvIGJlIGNoYW5nZWQgYWNjb3JkaW5nIHRvIHlvdXIgc2V0dXAuDQoNCiMgMTAuMSBSIHNlc3Npb24gaW5mb3JtYXRpb24NCg0KYGBge3Igc2Vzc2lvbl9pbmZvfQ0Kc2Vzc2lvbkluZm8oKQ0KYGBgDQoNCiMjIDEwLjIgUmVmZXJlbmNlcw0KDQohW10oQzovVXNlcnMvcGtsb29zdGVybWFuL0RvY3VtZW50cy9nZW5lcmFsX3dvcmtmbG93L2RlY29yYXRpb24tc3Ryb2tlLWZsYXQtdjIucG5nKQ0KDQpgYGB7cn0NCmtuaXRyOjprbml0X2V4aXQoKQ0KYGBgDQoNCmBgYHtyfQ0Ka25pdHI6OmtuaXRfZXhpdCgpDQpgYGANCg0KIyA5LjAgcmVsb2FkIGRhdGENCg0KYGBge3Igc3BhdGlhbF9kZWNvbl9wcmVwYXJlfQ0KI3JlbG9hZCBkYXRhDQojIERhdGEgPC0NCiMgICByZWFkTmFub1N0cmluZ0dlb014U2V0KGRjY0ZpbGVzID0gRENDRmlsZXMsDQojICAgICAgICAgICAgICAgICAgICAgICAgICBwa2NGaWxlcyA9IFBLQ0ZpbGVzLA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgcGhlbm9EYXRhRmlsZSA9IFNhbXBsZUFubm90YXRpb25GaWxlLA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgcGhlbm9EYXRhU2hlZXQgPSAiU2hlZXQxIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgIHBoZW5vRGF0YURjY0NvbE5hbWUgPSAiU2FtcGxlX0lEIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgIHByb3RvY29sRGF0YUNvbE5hbWVzID0gYygiYW9pIiwgInJvaSIpLA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgZXhwZXJpbWVudERhdGFDb2xOYW1lcyA9IGMoInBhbmVsIikpDQojIA0KIyBwa2NzIDwtIGFubm90YXRpb24oRGF0YSkNCiMgbW9kdWxlcyA8LSBnc3ViKCIucGtjIiwgIiIsIHBrY3MpDQojIA0KIyAjc2hpZnQgYW55IGV4cHJlc3Npb24gY291bnRzIHdpdGggYSB2YWx1ZSBvZiAwIHRvIDEgdG8gZW5hYmxlIGluIGRvd25zdHJlYW0gdHJhbnNmb3JtYXRpb25zLg0KIyBEYXRhIDwtIHNoaWZ0Q291bnRzT25lKERhdGEsIHVzZURBTG9naWMgPSBUUlVFKQ0KIyANCiMgI2NvbGxhcHNfdGFyZ2V0cw0KIyB0YXJnZXRfRGF0YSA8LSBhZ2dyZWdhdGVDb3VudHMoRGF0YSkNCiMgZGltKHRhcmdldF9EYXRhKQ0KIyANCiMgI25vcm1hbGl6ZQ0KIyB0YXJnZXRfRGF0YSA8LSBub3JtYWxpemUodGFyZ2V0X0RhdGEgLCBkYXRhX3R5cGUgPSAiUk5BIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtX21ldGhvZCA9ICJxdWFudCIsIA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlc2lyZWRRdWFudGlsZSA9IC43NSwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b0VsdCA9ICJxX25vcm0iKQ0KYGBgDQoNCiMgOS40IFJ1biBhZHZhbmNlZCBzcGF0aWFsIGRldm9uY29sdXRpb24NCg0KYGBge3J9DQojIHZlY3RvciBpZGVudGlmeWluZyBwdXJlIHR1bW9yIHNlZ21lbnRzOg0KdGFyZ2V0X0RhdGEkaXN0dW1vciA9IHRhcmdldF9EYXRhJEFOTjIgPT0gIkVwaXRoZWxpdW0iDQoNCiMgcnVuIHNwYXRpYWxkZWNvbiB3aXRoIGFsbCB0aGUgYmVsbHMgYW5kIHdoaXN0bGVzOg0KcmVzdGlscyA9IHJ1bnNwYXRpYWxkZWNvbihvYmplY3QgPSB0YXJnZXRfRGF0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbm9ybV9lbHQgPSAicV9ub3JtIiwgICAgICAgICAgICAgICAgICAgICMgbm9ybWFsaXplZCBkYXRhDQogICAgICAgICAgICAgICAgICAgICAgICAgIHJhd19lbHQgPSAiZXhwcnMiLCAgICAgICAgICAgICAgICAgICAgICAjIGV4cGVjdGVkIGJhY2tncm91bmQgY291bnRzIGZvciBldmVyeSBkYXRhIHBvaW50IGluIG5vcm0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgWCA9IHNhZmVUTUUsICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgc2FmZVRNRSBtYXRyaXgsIHVzZWQgYnkgZGVmYXVsdA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjZWxsbWVyZ2VzID0gc2FmZVRNRS5tYXRjaGVzLCAgICAgICAgICAgIyBzYWZlVE1FLm1hdGNoZXMgb2JqZWN0LCB1c2VkIGJ5IGRlZmF1bHQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbF9jb3VudHMgPSB0YXJnZXRfRGF0YSRudWNsZWksICAgICAgIyBudWNsZWkgY291bnRzLCB1c2VkIHRvIGVzdGltYXRlIHRvdGFsIGNlbGxzDQogICAgICAgICAgICAgICAgICAgICAgICAgIGlzX3B1cmVfdHVtb3IgPSB0YXJnZXRfRGF0YSRpc3R1bW9yLCAgICMgaWRlbnRpdGllcyBvZiB0aGUgVHVtb3Igc2VnbWVudHMvb2JzZXJ2YXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIG5fdHVtb3JfY2x1c3RlcnMgPSA1KSAgICAgICAgICAgICAgICAgICAjIGhvdyBtYW55IGRpc3RpbmN0IHR1bW9yIHByb2ZpbGVzIHRvIGFwcGVuZCB0byBzYWZlVE1FDQoNCiNzdHIocERhdGEocmVzdGlscykpDQpoZWF0bWFwKHN3ZWVwKHJlc3RpbHNAZXhwZXJpbWVudERhdGFAb3RoZXIkU3BhdGlhbERlY29uTWF0cml4LCAxLCBhcHBseShyZXN0aWxzQGV4cGVyaW1lbnREYXRhQG90aGVyJFNwYXRpYWxEZWNvbk1hdHJpeCwgMSwgbWF4KSwgIi8iKSwNCiAgICAgICAgIGxhYlJvdyA9IE5BLCBtYXJnaW5zID0gYygxMCwgNSkpDQoNCmBgYA0KDQojIDkuMy4yIFBsb3R0aW5nIGRlY29udm9sdXRpb24gcmVzdWx0cw0KDQpgYGB7ciwgZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9N30NCiMgRm9yIHJlZmVyZW5jZSwgc2hvdyB0aGUgVElMcyBjb2xvciBkYXRhIG9iamVjdCB1c2VkIGJ5IHRoZSBwbG90dGluZyBmdW5jdGlvbnMgDQojIHdoZW4gc2FmZVRNRSBoYXMgYmVlbiB1c2VkOg0KZGF0YSgiY2VsbGNvbHMiKQ0KY2VsbGNvbHMNCg0KbyA9IGhjbHVzdChkaXN0KHQocmVzJGNlbGwuY291bnRzJGNlbGwuY291bnRzKSkpJG9yZGVyDQpsYXlvdXQobWF0cml4KGMoMSwgMiksIDEpLCB3aWR0aHMgPSBjKDcsIDMpKQ0KVElMX2JhcnBsb3QodChyZXMkY2VsbC5jb3VudHMkY2VsbC5jb3VudHNbLCBvXSksDQogICAgICAgICAgICBob3JpeiA9IFRSVUUsIGRyYXdfbGVnZW5kID0gVFJVRSwgY2V4Lm5hbWVzID0gMC45KQ0KDQojcGFyKG1hcj1jKDIsIDE1LCAyLCAyKSkNCg0KIyBvciB0aGUgcHJvcG9ydGlvbnMgb2YgY2VsbHM6DQp0ZW1wID0gcmVwbGFjZShyZXMkcHJvcF9vZl9ub250dW1vciwgaXMubmEocmVzJHByb3Bfb2Zfbm9udHVtb3IpLCAwKQ0KbyA9IGhjbHVzdChkaXN0KHRlbXBbcmVzJEFOTjIgPT0gIkNENDUiLF0pKSRvcmRlcg0KVElMX2JhcnBsb3QodChyZXMkcHJvcF9vZl9hbGwpLCANCiAgICAgICAgICAgIGhvcml6ID0gVFJVRSwgZHJhd19sZWdlbmQgPSBUUlVFLCBjZXgubmFtZXMgPSAwLjkpDQoNCmBgYA0KDQojIDkuMy4zIEZsb3JldHMgb2YgU3BhdGlhbCBkZWNvbnZvbHV0aW9uDQoNClRoZSBzZWNvbmQgZnVuY3Rpb24gaXMgImZsb3JldHMiLCB1c2VkIGZvciBwbG90dGluZyBjZWxsIGFidW5kYW5jZXMgYXRvcA0Kc29tZSAyLUQgcHJvamVjdGlvbi4gSGVyZSwgd2UnbGwgcGxvdCBjZWxsIGFidW5kYW5jZXMgYXRvcCB0aGUgZmlyc3QgMg0KcHJpbmNpcGFsIGNvbXBvbmVudHMgb2YgdGhlIGRhdGE6DQoNCmBgYHtyfQ0KDQojTk9URTogc2VlbXMgdG8gY3Jhc2ggaWYgYWxsIGFzc2lnbm1lbnRzIGFyZSB6ZXJvcyBpbiBhbiBBT0k/DQoNCiMgUENBIG9mIHRoZSBub3JtYWxpemVkIGRhdGE6DQpub3JtPWFzc2F5RGF0YUVsZW1lbnQodGFyZ2V0X0RhdGEsZWx0PSJxX25vcm0iKQ0KcGMgPSBwcmNvbXAodChsb2cyKHBtYXgobm9ybSwgMSkpKSkkeFssIGMoMSwgMildDQoNCiMgDQojICMgcnVuIGZsb3JldHMgZnVuY3Rpb246DQpwYXIobWFyID0gYyg1LDUsMSwxKSkNCmxheW91dChtYXRyaXgoYygxLCAyKSwgMSksIHdpZHRocyA9IGMoNiwgMikpDQpmbG9yZXRzKHggPSBwY1sxOjE0LCAxXSwgeSA9IHBjWzE6MTQsIDJdLCBiID0gdChyZXMkYmV0YSlbMToxNCxdLCBjZXggPSAyLCB4bGFiID0gIlBDMSIsIHlsYWIgPSAiUEMyIikNCnBhcihtYXIgPSBjKDAsMCwwLDApKQ0KZnJhbWUoKQ0KbGVnZW5kKCJjZW50ZXIiLCBmaWxsID0gY2VsbGNvbHNbcm93bmFtZXModChyZXMkYmV0YSkpXSwNCiAgICAgICBsZWdlbmQgPSByb3duYW1lcyh0KHJlcyRiZXRhKSksIGNleCA9IDAuNykNCmBgYA0KDQojIDkuNCBjb21iaW5pbmcgY2VsbHR5cGVzDQoNCldoZW4gdHdvIGNlbGwgdHlwZXMgYXJlIHRvbyBzaW1pbGFyLCB0aGUgZXN0aW1hdGlvbiBvZiB0aGVpciBhYnVuZGFuY2VzDQpiZWNvbWVzIHVuc3RhYmxlLiBIb3dldmVyLCB0aGVpciBzdW0gY2FuIHN0aWxsIGJlIGVzdGltYXRlZCBlYXNpbHkuIFRoZQ0KZnVuY3Rpb24gImNvbGxhcHNlQ2VsbFR5cGVzIiB0YWtlcyBhIGRlY29udm9sdXRpb24gcmVzdWx0cyBvYmplY3QgYW5kDQpjb2xsYXBzZXMgYW55IGNvbHNlbHktcmVsYXRlZCBjZWxsIHR5cGVzIHlvdSB0ZWxsIGl0IHRvDQoNCmBgYHtyfQ0KbWF0Y2hpbmcgPSBsaXN0KCkNCm1hdGNoaW5nJG15ZWxvaWQgPSBjKCAibWFjcm9waGFnZXMiLCAibW9ub2N5dGVzIiwgIm1EQ3MiKQ0KbWF0Y2hpbmckVC5OSyA9IGMoIkNENC5ULmNlbGxzIiwiQ0Q4LlQuY2VsbHMiLCAiVHJlZyIsICJOSyIpDQptYXRjaGluZyRCID0gYygiQiIpDQptYXRjaGluZyRtYXN0ID0gYygibWFzdCIpDQptYXRjaGluZyRuZXV0cm9waGlscyA9IGMoIm5ldXRyb3BoaWxzIikNCm1hdGNoaW5nJHN0cm9tYSA9IGMoImVuZG90aGVsaWFsLmNlbGxzIiwgImZpYnJvYmxhc3RzIikNCg0KY29sbGFwc2VkID0gY29sbGFwc2VDZWxsVHlwZXMoZml0ID0gcmVzdGlscywgbWF0Y2hpbmcgPSBtYXRjaGluZykNCg0KaGVhdG1hcChjb2xsYXBzZWQkYmV0YSwgY2V4Um93ID0gMC44NSwgY2V4Q29sID0gMC43NSkNCmBgYA0K